From 94108d19b90278fff157181ebbf1713be7207be4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:48:24 +0000 Subject: [PATCH] Deploy to GitHub pages --- .nojekyll | 0 404.html | 22 ++ assets/app.KvA41C-Y.js | 13 + assets/chunks/VPAlgoliaSearchBox.ELVt2j7H.js | 17 + assets/chunks/framework.g9eZ-ZSs.js | 2 + assets/chunks/index.esm.VSePrB68.js | 7 + assets/chunks/theme.h7w36Qzb.js | 7 + assets/concepts_include-tree.md.tAg1rwYk.js | 111 ++++++ .../concepts_include-tree.md.tAg1rwYk.lean.js | 8 + assets/concepts_includes.md.va6Oh-E9.js | 50 +++ assets/concepts_includes.md.va6Oh-E9.lean.js | 32 ++ assets/history.md.ux281dRc.js | 1 + assets/history.md.ux281dRc.lean.js | 1 + assets/index.md.J5qzQ5SM.js | 1 + assets/index.md.J5qzQ5SM.lean.js | 1 + .../inter-italic-cyrillic-ext.OVycGSDq.woff2 | Bin 0 -> 28332 bytes assets/inter-italic-cyrillic.-nLMcIwj.woff2 | Bin 0 -> 17824 bytes assets/inter-italic-greek-ext.hznxWNZO.woff2 | Bin 0 -> 12188 bytes assets/inter-italic-greek.PSfer2Kc.woff2 | Bin 0 -> 23264 bytes assets/inter-italic-latin-ext.RnFly65-.woff2 | Bin 0 -> 63552 bytes assets/inter-italic-latin.27E69YJn.woff2 | Bin 0 -> 46048 bytes assets/inter-italic-vietnamese.xzQHe1q1.woff2 | Bin 0 -> 8784 bytes .../inter-roman-cyrillic-ext.8T9wMG5w.woff2 | Bin 0 -> 26600 bytes assets/inter-roman-cyrillic.jIZ9REo5.woff2 | Bin 0 -> 16780 bytes assets/inter-roman-greek-ext.9JiNzaSO.woff2 | Bin 0 -> 11808 bytes assets/inter-roman-greek.Cb5wWeGA.woff2 | Bin 0 -> 21776 bytes assets/inter-roman-latin-ext.GZWE-KO4.woff2 | Bin 0 -> 59608 bytes assets/inter-roman-latin.bvIUbFQP.woff2 | Bin 0 -> 42464 bytes assets/inter-roman-vietnamese.paY3CzEB.woff2 | Bin 0 -> 8492 bytes assets/introduction.md.aI2TP5X5.js | 1 + assets/introduction.md.aI2TP5X5.lean.js | 1 + ...model-components_attributes.md.IUIXuOcX.js | 1 + ...-components_attributes.md.IUIXuOcX.lean.js | 1 + ...ttributes_client-validation.md.1GRmSFWa.js | 31 ++ ...utes_client-validation.md.1GRmSFWa.lean.js | 22 ++ ...ponents_attributes_coalesce.md.Lkb6B2Rs.js | 1 + ...ts_attributes_coalesce.md.Lkb6B2Rs.lean.js | 1 + ...ttributes_controller-action.md.Ldygq9AT.js | 27 ++ ...utes_controller-action.md.Ldygq9AT.lean.js | 1 + ...nents_attributes_controller.md.4MsqYLok.js | 7 + ..._attributes_controller.md.4MsqYLok.lean.js | 1 + ...ttributes_create-controller.md.R_UFOdqz.js | 7 + ...utes_create-controller.md.R_UFOdqz.lean.js | 1 + ...onents_attributes_date-type.md.IriIMwEk.js | 7 + ...s_attributes_date-type.md.IriIMwEk.lean.js | 1 + ...attributes_default-order-by.md.2t7Z9gZk.js | 23 ++ ...butes_default-order-by.md.2t7Z9gZk.lean.js | 1 + ...butes_dto-includes-excludes.md.gWiALlZf.js | 40 +++ ..._dto-includes-excludes.md.gWiALlZf.lean.js | 22 ++ ...mponents_attributes_execute.md.OmkAEPLi.js | 11 + ...nts_attributes_execute.md.OmkAEPLi.lean.js | 1 + ...omponents_attributes_hidden.md.MV6N1IoA.js | 7 + ...ents_attributes_hidden.md.MV6N1IoA.lean.js | 1 + ...omponents_attributes_inject.md.wUXXw8WO.js | 12 + ...ents_attributes_inject.md.wUXXw8WO.lean.js | 1 + ...nts_attributes_internal-use.md.J0q2AD4y.js | 20 ++ ...ttributes_internal-use.md.J0q2AD4y.lean.js | 1 + ...onents_attributes_list-text.md.6SOGN1hl.js | 12 + ...s_attributes_list-text.md.6SOGN1hl.lean.js | 1 + ...butes_load-from-data-source.md.mGs5QFTJ.js | 11 + ..._load-from-data-source.md.mGs5QFTJ.lean.js | 1 + ...nts_attributes_many-to-many.md.66__Haw9.js | 9 + ...ttributes_many-to-many.md.66__Haw9.lean.js | 1 + ...ponents_attributes_restrict.md.LYUDlSbo.js | 23 ++ ...ts_attributes_restrict.md.LYUDlSbo.lean.js | 1 + ...omponents_attributes_search.md.q7FurM4k.js | 18 + ...ents_attributes_search.md.q7FurM4k.lean.js | 1 + ...tributes_security-attribute.md.jOlvg0wG.js | 27 ++ ...tes_security-attribute.md.jOlvg0wG.lean.js | 1 + ...ts_attributes_select-filter.md.O79G0Zlu.js | 19 ++ ...tributes_select-filter.md.O79G0Zlu.lean.js | 1 + ...tributes_typescript-partial.md.4Crd20VW.js | 7 + ...tes_typescript-partial.md.4Crd20VW.lean.js | 1 + ..._model-components_behaviors.md.34ydZMzc.js | 70 ++++ ...l-components_behaviors.md.34ydZMzc.lean.js | 3 + ...del-components_data-sources.md.bsBPSQ6R.js | 111 ++++++ ...omponents_data-sources.md.bsBPSQ6R.lean.js | 31 ++ ...ng_model-components_methods.md._iwSjDHF.js | 134 ++++++++ ...del-components_methods.md._iwSjDHF.lean.js | 12 + ...model-components_properties.md.gx6u8T0D.js | 1 + ...-components_properties.md.gx6u8T0D.lean.js | 1 + .../modeling_model-types_dtos.md.SyTF2MTh.js | 86 +++++ ...eling_model-types_dtos.md.SyTF2MTh.lean.js | 1 + ...deling_model-types_entities.md.kyLYfGOY.js | 48 +++ ...g_model-types_entities.md.kyLYfGOY.lean.js | 1 + ..._model-types_external-types.md.qdzqqDbb.js | 41 +++ ...l-types_external-types.md.qdzqqDbb.lean.js | 1 + ...deling_model-types_services.md.CHVSzKDm.js | 27 ++ ...g_model-types_services.md.CHVSzKDm.lean.js | 1 + assets/security-overview.lqdZeXXG.webp | Bin 0 -> 108183 bytes assets/stacks_agnostic_dtos.md.8_xJeZ2y.js | 1 + .../stacks_agnostic_dtos.md.8_xJeZ2y.lean.js | 1 + .../stacks_agnostic_generation.md.--UgUCa6.js | 18 + ...ks_agnostic_generation.md.--UgUCa6.lean.js | 1 + ...ic_getting-started-modeling.md.KbmYXIa-.js | 1 + ...tting-started-modeling.md.KbmYXIa-.lean.js | 1 + ...guation_external-view-model.md.m3dMwNWm.js | 1 + ...on_external-view-model.md.m3dMwNWm.lean.js | 1 + ...ambiguation_list-view-model.md.yRpM_Wq2.js | 1 + ...uation_list-view-model.md.yRpM_Wq2.lean.js | 1 + ...s_disambiguation_view-model.md.CJJYNLOF.js | 1 + ...ambiguation_view-model.md.CJJYNLOF.lean.js | 1 + .../stacks_ko_client_bindings.md.uLy2BLbT.js | 40 +++ ...cks_ko_client_bindings.md.uLy2BLbT.lean.js | 1 + ..._client_external-view-model.md.mJX84X3y.js | 8 + ...nt_external-view-model.md.mJX84X3y.lean.js | 8 + ...s_ko_client_list-view-model.md.sYLWdQOg.js | 12 + ...client_list-view-model.md.sYLWdQOg.lean.js | 10 + .../stacks_ko_client_methods.md.1ReCEXt-.js | 5 + ...acks_ko_client_methods.md.1ReCEXt-.lean.js | 1 + ...acks_ko_client_model-config.md.t5pvBrNy.js | 8 + ...ko_client_model-config.md.t5pvBrNy.lean.js | 8 + ...stacks_ko_client_view-model.md.cA7v0DUf.js | 21 ++ ...s_ko_client_view-model.md.cA7v0DUf.lean.js | 21 ++ .../stacks_ko_getting-started.md.KI5cX2XQ.js | 51 +++ ...cks_ko_getting-started.md.KI5cX2XQ.lean.js | 1 + assets/stacks_ko_overview.md.rAQfoqex.js | 1 + assets/stacks_ko_overview.md.rAQfoqex.lean.js | 1 + ...ents_c-admin-audit-log-page.md.GDNXyXjl.js | 14 + ...c-admin-audit-log-page.md.GDNXyXjl.lean.js | 1 + ..._components_c-admin-display.md.w8gdv3pP.js | 8 + ...onents_c-admin-display.md.w8gdv3pP.lean.js | 1 + ...ponents_c-admin-editor-page.md.SUSnocBA.js | 19 ++ ...ts_c-admin-editor-page.md.SUSnocBA.lean.js | 1 + ...y_components_c-admin-editor.md.oQcY14k_.js | 1 + ...ponents_c-admin-editor.md.oQcY14k_.lean.js | 1 + ...y_components_c-admin-method.md.b9u2lyQx.js | 1 + ...ponents_c-admin-method.md.b9u2lyQx.lean.js | 1 + ..._components_c-admin-methods.md.roY2qkqn.js | 1 + ...onents_c-admin-methods.md.roY2qkqn.lean.js | 1 + ...mponents_c-admin-table-page.md.a48uAuy8.js | 19 ++ ...nts_c-admin-table-page.md.a48uAuy8.lean.js | 1 + ...nents_c-admin-table-toolbar.md.hkVtE9hX.js | 1 + ..._c-admin-table-toolbar.md.hkVtE9hX.lean.js | 1 + ...fy_components_c-admin-table.md.PTEjWSuO.js | 1 + ...mponents_c-admin-table.md.PTEjWSuO.lean.js | 1 + ...omponents_c-datetime-picker.md.z5FrZ602.js | 10 + ...ents_c-datetime-picker.md.z5FrZ602.lean.js | 2 + ...uetify_components_c-display.md.BR23Vc5-.js | 6 + ...y_components_c-display.md.BR23Vc5-.lean.js | 2 + ...-vuetify_components_c-input.md.g15AvQ1z.js | 6 + ...ify_components_c-input.md.g15AvQ1z.lean.js | 2 + ...y_components_c-list-filters.md.3ww2oFHW.js | 1 + ...ponents_c-list-filters.md.3ww2oFHW.lean.js | 1 + ...components_c-list-page-size.md.iQFFEkp9.js | 1 + ...nents_c-list-page-size.md.iQFFEkp9.lean.js | 1 + ...tify_components_c-list-page.md.VoIz1Ye0.js | 1 + ...components_c-list-page.md.VoIz1Ye0.lean.js | 1 + ...omponents_c-list-pagination.md.dKBYttYP.js | 1 + ...ents_c-list-pagination.md.dKBYttYP.lean.js | 1 + ...onents_c-list-range-display.md.ahES0O89.js | 1 + ...s_c-list-range-display.md.ahES0O89.lean.js | 1 + ..._components_c-loader-status.md.FwR65IYg.js | 44 +++ ...onents_c-loader-status.md.FwR65IYg.lean.js | 12 + ...nents_c-select-many-to-many.md.kg745vjv.js | 10 + ..._c-select-many-to-many.md.kg745vjv.lean.js | 2 + ...nents_c-select-string-value.md.tV0v8rsN.js | 27 ++ ..._c-select-string-value.md.tV0v8rsN.lean.js | 1 + ..._components_c-select-values.md.mjZZaKzu.js | 5 + ...onents_c-select-values.md.mjZZaKzu.lean.js | 2 + ...vuetify_components_c-select.md.sYvwmnUu.js | 35 ++ ...fy_components_c-select.md.sYvwmnUu.lean.js | 5 + ...-vuetify_components_c-table.md.n25jKYu7.js | 17 + ...ify_components_c-table.md.n25jKYu7.lean.js | 1 + ...alesce-vue-vuetify_overview.md.SycRM1Rq.js | 1 + ...e-vue-vuetify_overview.md.SycRM1Rq.lean.js | 1 + .../stacks_vue_getting-started.md.2JJlpDJm.js | 39 +++ ...ks_vue_getting-started.md.2JJlpDJm.lean.js | 1 + ...acks_vue_layers_api-clients.md.BwZLF0NI.js | 52 +++ ...vue_layers_api-clients.md.BwZLF0NI.lean.js | 1 + .../stacks_vue_layers_metadata.md.m2bw93Lp.js | 1 + ...ks_vue_layers_metadata.md.m2bw93Lp.lean.js | 1 + .../stacks_vue_layers_models.md.hU96gV_Y.js | 77 +++++ ...acks_vue_layers_models.md.hU96gV_Y.lean.js | 43 +++ ...tacks_vue_layers_viewmodels.md.y7kFb0eu.js | 56 ++++ ..._vue_layers_viewmodels.md.y7kFb0eu.lean.js | 21 ++ assets/stacks_vue_overview.md.PKO1LKIV.js | 1 + .../stacks_vue_overview.md.PKO1LKIV.lean.js | 1 + assets/stacks_vue_vue2-to-vue3.md.hLyUCPCI.js | 177 ++++++++++ ...tacks_vue_vue2-to-vue3.md.hLyUCPCI.lean.js | 1 + assets/style.SedhU3_x.css | 1 + assets/topics_audit-logging.md.m_cBN5t5.js | 101 ++++++ .../topics_audit-logging.md.m_cBN5t5.lean.js | 1 + assets/topics_coalesce-json.md.U3jptpSC.js | 69 ++++ .../topics_coalesce-json.md.U3jptpSC.lean.js | 1 + assets/topics_security.md.DU-P7c3D.js | 291 ++++++++++++++++ assets/topics_security.md.DU-P7c3D.lean.js | 1 + assets/topics_startup.md.rzuiSq70.js | 28 ++ assets/topics_startup.md.rzuiSq70.lean.js | 1 + coalesce-horizontal-color.svg | 96 ++++++ coalesce-icon-color.svg | 36 ++ concepts/include-tree.html | 136 ++++++++ concepts/includes.html | 76 +++++ ef-logo.svg | 5 + favicon.ico | Bin 0 -> 15086 bytes hashmap.json | 1 + history.html | 26 ++ index.html | 26 ++ intellitect-text-black.svg | 59 ++++ intellitect-text-white.svg | 59 ++++ introduction.html | 26 ++ modeling/model-components/attributes.html | 26 ++ .../attributes/client-validation.html | 56 ++++ .../model-components/attributes/coalesce.html | 26 ++ .../attributes/controller-action.html | 53 +++ .../attributes/controller.html | 32 ++ .../attributes/create-controller.html | 34 ++ .../attributes/date-type.html | 33 ++ .../attributes/default-order-by.html | 50 +++ .../attributes/dto-includes-excludes.html | 66 ++++ .../model-components/attributes/execute.html | 36 ++ .../model-components/attributes/hidden.html | 33 ++ .../model-components/attributes/inject.html | 37 ++ .../attributes/internal-use.html | 45 +++ .../attributes/list-text.html | 37 ++ .../attributes/load-from-data-source.html | 37 ++ .../attributes/many-to-many.html | 35 ++ .../model-components/attributes/restrict.html | 48 +++ .../model-components/attributes/search.html | 43 +++ .../attributes/security-attribute.html | 54 +++ .../attributes/select-filter.html | 44 +++ .../attributes/typescript-partial.html | 32 ++ modeling/model-components/behaviors.html | 95 ++++++ modeling/model-components/data-sources.html | 136 ++++++++ modeling/model-components/methods.html | 159 +++++++++ modeling/model-components/properties.html | 26 ++ modeling/model-types/dtos.html | 111 ++++++ modeling/model-types/entities.html | 73 ++++ modeling/model-types/external-types.html | 66 ++++ modeling/model-types/services.html | 52 +++ net-logo.svg | 7 + stacks/agnostic/dtos.html | 26 ++ stacks/agnostic/generation.html | 43 +++ stacks/agnostic/getting-started-modeling.html | 26 ++ .../disambiguation/external-view-model.html | 26 ++ stacks/disambiguation/list-view-model.html | 26 ++ stacks/disambiguation/view-model.html | 26 ++ stacks/ko/client/bindings.html | 65 ++++ stacks/ko/client/external-view-model.html | 33 ++ stacks/ko/client/list-view-model.html | 37 ++ stacks/ko/client/methods.html | 30 ++ stacks/ko/client/model-config.html | 33 ++ stacks/ko/client/view-model.html | 47 +++ stacks/ko/getting-started.html | 76 +++++ stacks/ko/overview.html | 26 ++ .../components/c-admin-audit-log-page.html | 39 +++ .../components/c-admin-display.html | 33 ++ .../components/c-admin-editor-page.html | 44 +++ .../components/c-admin-editor.html | 26 ++ .../components/c-admin-method.html | 26 ++ .../components/c-admin-methods.html | 26 ++ .../components/c-admin-table-page.html | 44 +++ .../components/c-admin-table-toolbar.html | 26 ++ .../components/c-admin-table.html | 26 ++ .../components/c-datetime-picker.html | 35 ++ .../components/c-display.html | 31 ++ .../components/c-input.html | 31 ++ .../components/c-list-filters.html | 26 ++ .../components/c-list-page-size.html | 26 ++ .../components/c-list-page.html | 26 ++ .../components/c-list-pagination.html | 26 ++ .../components/c-list-range-display.html | 26 ++ .../components/c-loader-status.html | 69 ++++ .../components/c-select-many-to-many.html | 35 ++ .../components/c-select-string-value.html | 52 +++ .../components/c-select-values.html | 30 ++ .../components/c-select.html | 60 ++++ .../components/c-table.html | 42 +++ stacks/vue/coalesce-vue-vuetify/overview.html | 26 ++ stacks/vue/getting-started.html | 64 ++++ stacks/vue/layers/api-clients.html | 77 +++++ stacks/vue/layers/metadata.html | 26 ++ stacks/vue/layers/models.html | 102 ++++++ stacks/vue/layers/viewmodels.html | 81 +++++ stacks/vue/overview.html | 26 ++ stacks/vue/vue2-to-vue3.html | 202 +++++++++++ topics/audit-logging.html | 126 +++++++ topics/coalesce-json.html | 94 ++++++ topics/security.html | 316 ++++++++++++++++++ topics/startup.html | 53 +++ ts-logo-128.svg | 1 + ts-logo-512.svg | 1 + vite-logo.svg | 15 + vue-logo.svg | 8 + 284 files changed, 7365 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 assets/app.KvA41C-Y.js create mode 100644 assets/chunks/VPAlgoliaSearchBox.ELVt2j7H.js create mode 100644 assets/chunks/framework.g9eZ-ZSs.js create mode 100644 assets/chunks/index.esm.VSePrB68.js create mode 100644 assets/chunks/theme.h7w36Qzb.js create mode 100644 assets/concepts_include-tree.md.tAg1rwYk.js create mode 100644 assets/concepts_include-tree.md.tAg1rwYk.lean.js create mode 100644 assets/concepts_includes.md.va6Oh-E9.js create mode 100644 assets/concepts_includes.md.va6Oh-E9.lean.js create mode 100644 assets/history.md.ux281dRc.js create mode 100644 assets/history.md.ux281dRc.lean.js create mode 100644 assets/index.md.J5qzQ5SM.js create mode 100644 assets/index.md.J5qzQ5SM.lean.js create mode 100644 assets/inter-italic-cyrillic-ext.OVycGSDq.woff2 create mode 100644 assets/inter-italic-cyrillic.-nLMcIwj.woff2 create mode 100644 assets/inter-italic-greek-ext.hznxWNZO.woff2 create mode 100644 assets/inter-italic-greek.PSfer2Kc.woff2 create mode 100644 assets/inter-italic-latin-ext.RnFly65-.woff2 create mode 100644 assets/inter-italic-latin.27E69YJn.woff2 create mode 100644 assets/inter-italic-vietnamese.xzQHe1q1.woff2 create mode 100644 assets/inter-roman-cyrillic-ext.8T9wMG5w.woff2 create mode 100644 assets/inter-roman-cyrillic.jIZ9REo5.woff2 create mode 100644 assets/inter-roman-greek-ext.9JiNzaSO.woff2 create mode 100644 assets/inter-roman-greek.Cb5wWeGA.woff2 create mode 100644 assets/inter-roman-latin-ext.GZWE-KO4.woff2 create mode 100644 assets/inter-roman-latin.bvIUbFQP.woff2 create mode 100644 assets/inter-roman-vietnamese.paY3CzEB.woff2 create mode 100644 assets/introduction.md.aI2TP5X5.js create mode 100644 assets/introduction.md.aI2TP5X5.lean.js create mode 100644 assets/modeling_model-components_attributes.md.IUIXuOcX.js create mode 100644 assets/modeling_model-components_attributes.md.IUIXuOcX.lean.js create mode 100644 assets/modeling_model-components_attributes_client-validation.md.1GRmSFWa.js create mode 100644 assets/modeling_model-components_attributes_client-validation.md.1GRmSFWa.lean.js create mode 100644 assets/modeling_model-components_attributes_coalesce.md.Lkb6B2Rs.js create mode 100644 assets/modeling_model-components_attributes_coalesce.md.Lkb6B2Rs.lean.js create mode 100644 assets/modeling_model-components_attributes_controller-action.md.Ldygq9AT.js create mode 100644 assets/modeling_model-components_attributes_controller-action.md.Ldygq9AT.lean.js create mode 100644 assets/modeling_model-components_attributes_controller.md.4MsqYLok.js create mode 100644 assets/modeling_model-components_attributes_controller.md.4MsqYLok.lean.js create mode 100644 assets/modeling_model-components_attributes_create-controller.md.R_UFOdqz.js create mode 100644 assets/modeling_model-components_attributes_create-controller.md.R_UFOdqz.lean.js create mode 100644 assets/modeling_model-components_attributes_date-type.md.IriIMwEk.js create mode 100644 assets/modeling_model-components_attributes_date-type.md.IriIMwEk.lean.js create mode 100644 assets/modeling_model-components_attributes_default-order-by.md.2t7Z9gZk.js create mode 100644 assets/modeling_model-components_attributes_default-order-by.md.2t7Z9gZk.lean.js create mode 100644 assets/modeling_model-components_attributes_dto-includes-excludes.md.gWiALlZf.js create mode 100644 assets/modeling_model-components_attributes_dto-includes-excludes.md.gWiALlZf.lean.js create mode 100644 assets/modeling_model-components_attributes_execute.md.OmkAEPLi.js create mode 100644 assets/modeling_model-components_attributes_execute.md.OmkAEPLi.lean.js create mode 100644 assets/modeling_model-components_attributes_hidden.md.MV6N1IoA.js create mode 100644 assets/modeling_model-components_attributes_hidden.md.MV6N1IoA.lean.js create mode 100644 assets/modeling_model-components_attributes_inject.md.wUXXw8WO.js create mode 100644 assets/modeling_model-components_attributes_inject.md.wUXXw8WO.lean.js create mode 100644 assets/modeling_model-components_attributes_internal-use.md.J0q2AD4y.js create mode 100644 assets/modeling_model-components_attributes_internal-use.md.J0q2AD4y.lean.js create mode 100644 assets/modeling_model-components_attributes_list-text.md.6SOGN1hl.js create mode 100644 assets/modeling_model-components_attributes_list-text.md.6SOGN1hl.lean.js create mode 100644 assets/modeling_model-components_attributes_load-from-data-source.md.mGs5QFTJ.js create mode 100644 assets/modeling_model-components_attributes_load-from-data-source.md.mGs5QFTJ.lean.js create mode 100644 assets/modeling_model-components_attributes_many-to-many.md.66__Haw9.js create mode 100644 assets/modeling_model-components_attributes_many-to-many.md.66__Haw9.lean.js create mode 100644 assets/modeling_model-components_attributes_restrict.md.LYUDlSbo.js create mode 100644 assets/modeling_model-components_attributes_restrict.md.LYUDlSbo.lean.js create mode 100644 assets/modeling_model-components_attributes_search.md.q7FurM4k.js create mode 100644 assets/modeling_model-components_attributes_search.md.q7FurM4k.lean.js create mode 100644 assets/modeling_model-components_attributes_security-attribute.md.jOlvg0wG.js create mode 100644 assets/modeling_model-components_attributes_security-attribute.md.jOlvg0wG.lean.js create mode 100644 assets/modeling_model-components_attributes_select-filter.md.O79G0Zlu.js create mode 100644 assets/modeling_model-components_attributes_select-filter.md.O79G0Zlu.lean.js create mode 100644 assets/modeling_model-components_attributes_typescript-partial.md.4Crd20VW.js create mode 100644 assets/modeling_model-components_attributes_typescript-partial.md.4Crd20VW.lean.js create mode 100644 assets/modeling_model-components_behaviors.md.34ydZMzc.js create mode 100644 assets/modeling_model-components_behaviors.md.34ydZMzc.lean.js create mode 100644 assets/modeling_model-components_data-sources.md.bsBPSQ6R.js create mode 100644 assets/modeling_model-components_data-sources.md.bsBPSQ6R.lean.js create mode 100644 assets/modeling_model-components_methods.md._iwSjDHF.js create mode 100644 assets/modeling_model-components_methods.md._iwSjDHF.lean.js create mode 100644 assets/modeling_model-components_properties.md.gx6u8T0D.js create mode 100644 assets/modeling_model-components_properties.md.gx6u8T0D.lean.js create mode 100644 assets/modeling_model-types_dtos.md.SyTF2MTh.js create mode 100644 assets/modeling_model-types_dtos.md.SyTF2MTh.lean.js create mode 100644 assets/modeling_model-types_entities.md.kyLYfGOY.js create mode 100644 assets/modeling_model-types_entities.md.kyLYfGOY.lean.js create mode 100644 assets/modeling_model-types_external-types.md.qdzqqDbb.js create mode 100644 assets/modeling_model-types_external-types.md.qdzqqDbb.lean.js create mode 100644 assets/modeling_model-types_services.md.CHVSzKDm.js create mode 100644 assets/modeling_model-types_services.md.CHVSzKDm.lean.js create mode 100644 assets/security-overview.lqdZeXXG.webp create mode 100644 assets/stacks_agnostic_dtos.md.8_xJeZ2y.js create mode 100644 assets/stacks_agnostic_dtos.md.8_xJeZ2y.lean.js create mode 100644 assets/stacks_agnostic_generation.md.--UgUCa6.js create mode 100644 assets/stacks_agnostic_generation.md.--UgUCa6.lean.js create mode 100644 assets/stacks_agnostic_getting-started-modeling.md.KbmYXIa-.js create mode 100644 assets/stacks_agnostic_getting-started-modeling.md.KbmYXIa-.lean.js create mode 100644 assets/stacks_disambiguation_external-view-model.md.m3dMwNWm.js create mode 100644 assets/stacks_disambiguation_external-view-model.md.m3dMwNWm.lean.js create mode 100644 assets/stacks_disambiguation_list-view-model.md.yRpM_Wq2.js create mode 100644 assets/stacks_disambiguation_list-view-model.md.yRpM_Wq2.lean.js create mode 100644 assets/stacks_disambiguation_view-model.md.CJJYNLOF.js create mode 100644 assets/stacks_disambiguation_view-model.md.CJJYNLOF.lean.js create mode 100644 assets/stacks_ko_client_bindings.md.uLy2BLbT.js create mode 100644 assets/stacks_ko_client_bindings.md.uLy2BLbT.lean.js create mode 100644 assets/stacks_ko_client_external-view-model.md.mJX84X3y.js create mode 100644 assets/stacks_ko_client_external-view-model.md.mJX84X3y.lean.js create mode 100644 assets/stacks_ko_client_list-view-model.md.sYLWdQOg.js create mode 100644 assets/stacks_ko_client_list-view-model.md.sYLWdQOg.lean.js create mode 100644 assets/stacks_ko_client_methods.md.1ReCEXt-.js create mode 100644 assets/stacks_ko_client_methods.md.1ReCEXt-.lean.js create mode 100644 assets/stacks_ko_client_model-config.md.t5pvBrNy.js create mode 100644 assets/stacks_ko_client_model-config.md.t5pvBrNy.lean.js create mode 100644 assets/stacks_ko_client_view-model.md.cA7v0DUf.js create mode 100644 assets/stacks_ko_client_view-model.md.cA7v0DUf.lean.js create mode 100644 assets/stacks_ko_getting-started.md.KI5cX2XQ.js create mode 100644 assets/stacks_ko_getting-started.md.KI5cX2XQ.lean.js create mode 100644 assets/stacks_ko_overview.md.rAQfoqex.js create mode 100644 assets/stacks_ko_overview.md.rAQfoqex.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-audit-log-page.md.GDNXyXjl.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-audit-log-page.md.GDNXyXjl.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-display.md.w8gdv3pP.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-display.md.w8gdv3pP.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor-page.md.SUSnocBA.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor-page.md.SUSnocBA.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor.md.oQcY14k_.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor.md.oQcY14k_.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-method.md.b9u2lyQx.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-method.md.b9u2lyQx.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-methods.md.roY2qkqn.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-methods.md.roY2qkqn.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-page.md.a48uAuy8.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-page.md.a48uAuy8.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-toolbar.md.hkVtE9hX.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-toolbar.md.hkVtE9hX.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table.md.PTEjWSuO.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table.md.PTEjWSuO.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-datetime-picker.md.z5FrZ602.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-datetime-picker.md.z5FrZ602.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-display.md.BR23Vc5-.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-display.md.BR23Vc5-.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-input.md.g15AvQ1z.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-input.md.g15AvQ1z.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-list-filters.md.3ww2oFHW.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-list-filters.md.3ww2oFHW.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page-size.md.iQFFEkp9.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page-size.md.iQFFEkp9.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page.md.VoIz1Ye0.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page.md.VoIz1Ye0.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-list-pagination.md.dKBYttYP.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-list-pagination.md.dKBYttYP.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-list-range-display.md.ahES0O89.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-list-range-display.md.ahES0O89.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-loader-status.md.FwR65IYg.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-loader-status.md.FwR65IYg.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-select-many-to-many.md.kg745vjv.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-select-many-to-many.md.kg745vjv.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-select-string-value.md.tV0v8rsN.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-select-string-value.md.tV0v8rsN.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-select-values.md.mjZZaKzu.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-select-values.md.mjZZaKzu.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-select.md.sYvwmnUu.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-select.md.sYvwmnUu.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-table.md.n25jKYu7.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_components_c-table.md.n25jKYu7.lean.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_overview.md.SycRM1Rq.js create mode 100644 assets/stacks_vue_coalesce-vue-vuetify_overview.md.SycRM1Rq.lean.js create mode 100644 assets/stacks_vue_getting-started.md.2JJlpDJm.js create mode 100644 assets/stacks_vue_getting-started.md.2JJlpDJm.lean.js create mode 100644 assets/stacks_vue_layers_api-clients.md.BwZLF0NI.js create mode 100644 assets/stacks_vue_layers_api-clients.md.BwZLF0NI.lean.js create mode 100644 assets/stacks_vue_layers_metadata.md.m2bw93Lp.js create mode 100644 assets/stacks_vue_layers_metadata.md.m2bw93Lp.lean.js create mode 100644 assets/stacks_vue_layers_models.md.hU96gV_Y.js create mode 100644 assets/stacks_vue_layers_models.md.hU96gV_Y.lean.js create mode 100644 assets/stacks_vue_layers_viewmodels.md.y7kFb0eu.js create mode 100644 assets/stacks_vue_layers_viewmodels.md.y7kFb0eu.lean.js create mode 100644 assets/stacks_vue_overview.md.PKO1LKIV.js create mode 100644 assets/stacks_vue_overview.md.PKO1LKIV.lean.js create mode 100644 assets/stacks_vue_vue2-to-vue3.md.hLyUCPCI.js create mode 100644 assets/stacks_vue_vue2-to-vue3.md.hLyUCPCI.lean.js create mode 100644 assets/style.SedhU3_x.css create mode 100644 assets/topics_audit-logging.md.m_cBN5t5.js create mode 100644 assets/topics_audit-logging.md.m_cBN5t5.lean.js create mode 100644 assets/topics_coalesce-json.md.U3jptpSC.js create mode 100644 assets/topics_coalesce-json.md.U3jptpSC.lean.js create mode 100644 assets/topics_security.md.DU-P7c3D.js create mode 100644 assets/topics_security.md.DU-P7c3D.lean.js create mode 100644 assets/topics_startup.md.rzuiSq70.js create mode 100644 assets/topics_startup.md.rzuiSq70.lean.js create mode 100644 coalesce-horizontal-color.svg create mode 100644 coalesce-icon-color.svg create mode 100644 concepts/include-tree.html create mode 100644 concepts/includes.html create mode 100644 ef-logo.svg create mode 100644 favicon.ico create mode 100644 hashmap.json create mode 100644 history.html create mode 100644 index.html create mode 100644 intellitect-text-black.svg create mode 100644 intellitect-text-white.svg create mode 100644 introduction.html create mode 100644 modeling/model-components/attributes.html create mode 100644 modeling/model-components/attributes/client-validation.html create mode 100644 modeling/model-components/attributes/coalesce.html create mode 100644 modeling/model-components/attributes/controller-action.html create mode 100644 modeling/model-components/attributes/controller.html create mode 100644 modeling/model-components/attributes/create-controller.html create mode 100644 modeling/model-components/attributes/date-type.html create mode 100644 modeling/model-components/attributes/default-order-by.html create mode 100644 modeling/model-components/attributes/dto-includes-excludes.html create mode 100644 modeling/model-components/attributes/execute.html create mode 100644 modeling/model-components/attributes/hidden.html create mode 100644 modeling/model-components/attributes/inject.html create mode 100644 modeling/model-components/attributes/internal-use.html create mode 100644 modeling/model-components/attributes/list-text.html create mode 100644 modeling/model-components/attributes/load-from-data-source.html create mode 100644 modeling/model-components/attributes/many-to-many.html create mode 100644 modeling/model-components/attributes/restrict.html create mode 100644 modeling/model-components/attributes/search.html create mode 100644 modeling/model-components/attributes/security-attribute.html create mode 100644 modeling/model-components/attributes/select-filter.html create mode 100644 modeling/model-components/attributes/typescript-partial.html create mode 100644 modeling/model-components/behaviors.html create mode 100644 modeling/model-components/data-sources.html create mode 100644 modeling/model-components/methods.html create mode 100644 modeling/model-components/properties.html create mode 100644 modeling/model-types/dtos.html create mode 100644 modeling/model-types/entities.html create mode 100644 modeling/model-types/external-types.html create mode 100644 modeling/model-types/services.html create mode 100644 net-logo.svg create mode 100644 stacks/agnostic/dtos.html create mode 100644 stacks/agnostic/generation.html create mode 100644 stacks/agnostic/getting-started-modeling.html create mode 100644 stacks/disambiguation/external-view-model.html create mode 100644 stacks/disambiguation/list-view-model.html create mode 100644 stacks/disambiguation/view-model.html create mode 100644 stacks/ko/client/bindings.html create mode 100644 stacks/ko/client/external-view-model.html create mode 100644 stacks/ko/client/list-view-model.html create mode 100644 stacks/ko/client/methods.html create mode 100644 stacks/ko/client/model-config.html create mode 100644 stacks/ko/client/view-model.html create mode 100644 stacks/ko/getting-started.html create mode 100644 stacks/ko/overview.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-admin-display.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-admin-editor-page.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-admin-method.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-admin-methods.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-admin-table-toolbar.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-admin-table.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-datetime-picker.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-display.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-input.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-list-filters.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-list-page.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-list-range-display.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-loader-status.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-select-many-to-many.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-select-string-value.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-select-values.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-select.html create mode 100644 stacks/vue/coalesce-vue-vuetify/components/c-table.html create mode 100644 stacks/vue/coalesce-vue-vuetify/overview.html create mode 100644 stacks/vue/getting-started.html create mode 100644 stacks/vue/layers/api-clients.html create mode 100644 stacks/vue/layers/metadata.html create mode 100644 stacks/vue/layers/models.html create mode 100644 stacks/vue/layers/viewmodels.html create mode 100644 stacks/vue/overview.html create mode 100644 stacks/vue/vue2-to-vue3.html create mode 100644 topics/audit-logging.html create mode 100644 topics/coalesce-json.html create mode 100644 topics/security.html create mode 100644 topics/startup.html create mode 100644 ts-logo-128.svg create mode 100644 ts-logo-512.svg create mode 100644 vite-logo.svg create mode 100644 vue-logo.svg diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/404.html b/404.html new file mode 100644 index 000000000..38db4e596 --- /dev/null +++ b/404.html @@ -0,0 +1,22 @@ + + + + + + 404 | Coalesce + + + + + + + + + + + +
Skip to content

404

PAGE NOT FOUND

But if you don't change your direction, and if you keep looking, you may end up where you are heading.
+ + + + \ No newline at end of file diff --git a/assets/app.KvA41C-Y.js b/assets/app.KvA41C-Y.js new file mode 100644 index 000000000..4be5cc60d --- /dev/null +++ b/assets/app.KvA41C-Y.js @@ -0,0 +1,13 @@ +import{a9 as P,_,aa as T,o,c as r,k as u,F as b,E as y,n as v,t as A,ab as x,ac as D,r as E,d as C,X as S,a as L,R as O,ad as I,ae as j,af as f,v as m,ag as H,ah as V,ai as F,aj as R,ak as M,al as N,am as B,an as K,ao as q,ap as z,u as U,j as Y,z as G,aq as W,ar as X,as as J}from"./chunks/framework.g9eZ-ZSs.js";import{t as w}from"./chunks/theme.h7w36Qzb.js";const h={groups:{default:{vue:"Vue",knockout:"Knockout"},vue:{options:"Options API",setup:"Composition API"},"vue-bundler":{"vue-cli":"Vue CLI",vite:"Vite"}}},p=P({}),Q={props:{name:{type:String,default:"default"},isolated:{type:Boolean,default:!1},languages:{type:Object,required:!1}},data(){return{selectedLanguage:null,actualLanguages:{}}},computed:{localStorageKey(){return`vuepress-plugin-code-switcher@${this.name}`}},methods:{switchLanguage(e){if(this.isolated)return this.selectedLanguage=e;typeof localStorage<"u"&&localStorage.setItem(this.localStorageKey,e),p[this.name]=e},setConfiguredDefaultLanguages(){this.languages?this.actualLanguages=this.languages:h&&h.groups&&h.groups[this.name]&&(this.actualLanguages=h.groups[this.name]),this.selectedLanguage=Object.keys(this.actualLanguages)[0],p[this.name]||(p[this.name]=this.selectedLanguage)}},created(){if(!this.isolated){if(this.setConfiguredDefaultLanguages(),!this.actualLanguages)throw new Error('You must specify either the "languages" prop or use the "groups" option when configuring the plugin.');if(typeof localStorage<"u"){let e=localStorage.getItem(this.localStorageKey);e&&Object.keys(this.actualLanguages).indexOf(e)!==-1&&(this.selectedLanguage=e)}T(()=>this.selectedLanguage=p[this.name])}}},Z={class:"code-tabs"},ee={class:"code-tabs__nav"},te={class:"code-tabs__ul"},ae=["aria-pressed","aria-expanded","onClick"],se=["aria-selected"];function ie(e,t,a,n,i,l){return o(),r("div",Z,[u("div",ee,[u("ul",te,[(o(!0),r(b,null,y(i.actualLanguages,(d,s)=>(o(),r("li",{key:s,class:"code-tabs__li"},[u("button",{class:v(["code-tabs__nav-tab",{"code-tabs__nav-tab-active":i.selectedLanguage===s}]),"aria-pressed":i.selectedLanguage===s,"aria-expanded":i.selectedLanguage===s,onClick:Ae=>l.switchLanguage(s)},A(d),11,ae)]))),128))])]),(o(!0),r(b,null,y(i.actualLanguages,(d,s)=>x((o(),r("div",{key:s,class:v(["code-tabs-item",{"code-tabs-item__active":i.selectedLanguage===s}]),"aria-selected":i.selectedLanguage===s},[E(e.$slots,s)],10,se)),[[D,s===i.selectedLanguage]])),128))])}const ne=_(Q,[["render",ie]]);var g=new Map;async function oe(e){const t=await S(()=>import("./chunks/index.esm.VSePrB68.js"),__vite__mapDeps([]));return t.setCDN("https://unpkg.com/shiki/"),{highlighter:await t.getHighlighter({theme:"dark-plus",langs:[e]}),shiki:t}}function re(e){if(g.has(e))return g.get(e);const t=oe(e);return g.set(e,t),t}const ce=C({props:{def:{type:String,required:!0},lang:{type:String,default:"c#"},ctor:{type:[Number,String],default:null},noClass:{type:Boolean,default:!1},id:{type:String,default:null},idPrefix:{type:String,default:"member"}},data(){return{idAttr:"",html:""}},async serverPrefetch(){await this.renderHtml()},beforeMount(){var e,t;this.html=(e=this.$el)==null?void 0:e.innerHTML,this.idAttr=(t=this.$el)==null?void 0:t.id,this.html||this.$watch("def",()=>this.renderHtml(),{immediate:!0})},methods:{async renderHtml(){var e=null;this.ctor&&(e="// Also settable via constructor parameter #"+this.ctor,(this.lang=="c#"||this.lang=="csharp")&&!this.def.match(/\bset\b/)&&(e="// ONLY settable via constructor parameter #"+this.ctor));const t=(this.noClass?"":"public class x {")+(e?` +`+e:"")+` +`+this.def+(this.noClass?"":` +}`);if(this.id)this.idAttr=this.id;else if(this.lang=="ts"){const s=/(?:(?:readonly|public|static|protected|private|abstract|export) )*((?:namespace )?[\w$-]+)/.exec(this.def);this.idAttr=s?this.idPrefix+"-"+s[1]:null}else if(this.lang=="c#"){const s=/ (\w+)(?:<|\(| \{|$)/.exec(this.def);this.idAttr=s?this.idPrefix+"-"+s[1]:null}if(!this.idAttr)throw new Error("Unable to compute id for Prop "+this.def);this.idAttr=this.idAttr.toLowerCase().replace(/[ &<>"']/g,"-").replace(/\$/g,"_");const{highlighter:a,shiki:n}=await re(this.lang),i=a.codeToThemedTokens(t,this.lang);this.noClass||(i.shift(),i.pop());const l=a.getTheme();let d=n.renderToHtml(i,{fg:l.fg,bg:l.bg});this.html=`${d}`}}}),le=["innerHTML","id"],ue=["id"],de={class:"shiki",style:{"line-height":"1.18","padding-top":"1px","padding-bottom":"4px"}};function he(e,t,a,n,i,l){return e.html?(o(),r("h4",{key:0,innerHTML:e.html,class:"code-prop",id:e.idAttr},null,8,le)):(o(),r("h4",{key:1,class:"code-prop",id:e.idAttr},[u("pre",de,[L(" "),u("code",null,` + `+A(e.def)+` + `,1),L(` + `)])],8,ue))}const pe=_(ce,[["render",he]]),ge={},fe={class:"vp-doc intellitect-footer"},me=O('

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

',2),_e=[me];function be(e,t){return o(),r("footer",fe,_e)}const k=_(ge,[["render",be]]),ye={extends:w,Layout:()=>f(w.Layout,null,{"doc-bottom":()=>f(k,{class:"page-footer"})}),enhanceApp({app:e,router:t,siteData:a}){e.component("CodeTabs",ne),e.component("Prop",pe),e.component("SiteFooter",k)}};function $(e){if(e.extends){const t=$(e.extends);return{...t,...e,async enhanceApp(a){t.enhanceApp&&await t.enhanceApp(a),e.enhanceApp&&await e.enhanceApp(a)}}}return e}const c=$(ye),ve=C({name:"VitePressApp",setup(){const{site:e}=U();return Y(()=>{G(()=>{document.documentElement.lang=e.value.lang,document.documentElement.dir=e.value.dir})}),e.value.router.prefetchLinks&&W(),X(),J(),c.setup&&c.setup(),()=>f(c.Layout)}});async function Le(){const e=ke(),t=we();t.provide(V,e);const a=F(e.route);return t.provide(R,a),t.component("Content",M),t.component("ClientOnly",N),Object.defineProperties(t.config.globalProperties,{$frontmatter:{get(){return a.frontmatter.value}},$params:{get(){return a.page.value.params}}}),c.enhanceApp&&await c.enhanceApp({app:t,router:e,siteData:B}),{app:t,router:e,data:a}}function we(){return K(ve)}function ke(){let e=m,t;return q(a=>{let n=z(a),i=null;return n&&(e&&(t=n),(e||t===n)&&(n=n.replace(/\.js$/,".lean.js")),i=S(()=>import(n),__vite__mapDeps([]))),m&&(e=!1),i},c.NotFound)}m&&Le().then(({app:e,router:t,data:a})=>{t.go().then(()=>{H(t.route,a.site),e.mount("#app")})});export{Le as createApp}; +function __vite__mapDeps(indexes) { + if (!__vite__mapDeps.viteFileDeps) { + __vite__mapDeps.viteFileDeps = [] + } + return indexes.map((i) => __vite__mapDeps.viteFileDeps[i]) +} \ No newline at end of file diff --git a/assets/chunks/VPAlgoliaSearchBox.ELVt2j7H.js b/assets/chunks/VPAlgoliaSearchBox.ELVt2j7H.js new file mode 100644 index 000000000..32e85d45a --- /dev/null +++ b/assets/chunks/VPAlgoliaSearchBox.ELVt2j7H.js @@ -0,0 +1,17 @@ +import{d as so,at as fo,L as mo,j as po,y as vo,P as ho,o as yo,c as go}from"./framework.g9eZ-ZSs.js";import{u as bo}from"./theme.h7w36Qzb.js";/*! @docsearch/js 3.5.2 | MIT License | © Algolia, Inc. and contributors | https://docsearch.algolia.com */function ur(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),r.push.apply(r,n)}return r}function I(t){for(var e=1;e=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function se(t,e){return function(r){if(Array.isArray(r))return r}(t)||function(r,n){var o=r==null?null:typeof Symbol<"u"&&r[Symbol.iterator]||r["@@iterator"];if(o!=null){var i,a,c=[],u=!0,s=!1;try{for(o=o.call(r);!(u=(i=o.next()).done)&&(c.push(i.value),!n||c.length!==n);u=!0);}catch(l){s=!0,a=l}finally{try{u||o.return==null||o.return()}finally{if(s)throw a}}return c}}(t,e)||yn(t,e)||function(){throw new TypeError(`Invalid attempt to destructure non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}function ft(t){return function(e){if(Array.isArray(e))return Lt(e)}(t)||function(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}(t)||yn(t)||function(){throw new TypeError(`Invalid attempt to spread non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}function yn(t,e){if(t){if(typeof t=="string")return Lt(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return r==="Object"&&t.constructor&&(r=t.constructor.name),r==="Map"||r==="Set"?Array.from(t):r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?Lt(t,e):void 0}}function Lt(t,e){(e==null||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r3)for(r=[r],i=3;i0?Ie(v.type,v.props,v.key,null,v.__v):v)!=null){if(v.__=r,v.__b=r.__b+1,(p=b[l])===null||p&&v.key==p.key&&v.type===p.type)b[l]=void 0;else for(m=0;m<_;m++){if((p=b[m])&&v.key==p.key&&v.type===p.type){b[m]=void 0;break}p=null}Yt(t,v,p=p||mt,o,i,a,c,u,s),d=v.__e,(m=v.ref)&&p.ref!=m&&(y||(y=[]),p.ref&&y.push(p.ref,null,v),y.push(m,v.__c||d,v)),d!=null?(h==null&&(h=d),typeof v.type=="function"&&v.__k!=null&&v.__k===p.__k?v.__d=u=wn(v,u,t):u=jn(t,v,p,b,d,u),s||r.type!=="option"?typeof r.type=="function"&&(r.__d=u):t.value=""):u&&p.__e==u&&u.parentNode!=t&&(u=We(p))}for(r.__e=h,l=_;l--;)b[l]!=null&&(typeof r.type=="function"&&b[l].__e!=null&&b[l].__e==r.__d&&(r.__d=We(n,l+1)),In(b[l],b[l]));if(y)for(l=0;l3)for(r=[r],i=3;i=r.__.length&&r.__.push({}),r.__[t]}function kn(t){return pe=1,An(xn,t)}function An(t,e,r){var n=Je(de++,2);return n.t=t,n.__c||(n.__=[r?r(e):xn(void 0,e),function(o){var i=n.t(n.__[0],o);n.__[0]!==i&&(n.__=[i,n.__[1]],n.__c.setState({}))}],n.__c=q),n.__}function Cn(t,e){var r=Je(de++,3);!w.__s&&Gt(r.__H,e)&&(r.__=t,r.__H=e,q.__H.__h.push(r))}function gr(t,e){var r=Je(de++,4);!w.__s&&Gt(r.__H,e)&&(r.__=t,r.__H=e,q.__h.push(r))}function Pt(t,e){var r=Je(de++,7);return Gt(r.__H,e)&&(r.__=t(),r.__H=e,r.__h=t),r.__}function Po(){Ht.forEach(function(t){if(t.__P)try{t.__H.__h.forEach(ut),t.__H.__h.forEach(Ut),t.__H.__h=[]}catch(e){t.__H.__h=[],w.__e(e,t.__v)}}),Ht=[]}w.__b=function(t){q=null,pr&&pr(t)},w.__r=function(t){vr&&vr(t),de=0;var e=(q=t.__c).__H;e&&(e.__h.forEach(ut),e.__h.forEach(Ut),e.__h=[])},w.diffed=function(t){dr&&dr(t);var e=t.__c;e&&e.__H&&e.__H.__h.length&&(Ht.push(e)!==1&&mr===w.requestAnimationFrame||((mr=w.requestAnimationFrame)||function(r){var n,o=function(){clearTimeout(i),br&&cancelAnimationFrame(n),setTimeout(r)},i=setTimeout(o,100);br&&(n=requestAnimationFrame(o))})(Po)),q=void 0},w.__c=function(t,e){e.some(function(r){try{r.__h.forEach(ut),r.__h=r.__h.filter(function(n){return!n.__||Ut(n)})}catch(n){e.some(function(o){o.__h&&(o.__h=[])}),e=[],w.__e(n,r.__v)}}),hr&&hr(t,e)},w.unmount=function(t){yr&&yr(t);var e=t.__c;if(e&&e.__H)try{e.__H.__.forEach(ut)}catch(r){w.__e(r,e.__v)}};var br=typeof requestAnimationFrame=="function";function ut(t){var e=q;typeof t.__c=="function"&&t.__c(),q=e}function Ut(t){var e=q;t.__c=t.__(),q=e}function Gt(t,e){return!t||t.length!==e.length||e.some(function(r,n){return r!==t[n]})}function xn(t,e){return typeof e=="function"?e(t):e}function Nn(t,e){for(var r in e)t[r]=e[r];return t}function Ft(t,e){for(var r in t)if(r!=="__source"&&!(r in e))return!0;for(var n in e)if(n!=="__source"&&t[n]!==e[n])return!0;return!1}function Bt(t){this.props=t}(Bt.prototype=new K).isPureReactComponent=!0,Bt.prototype.shouldComponentUpdate=function(t,e){return Ft(this.props,t)||Ft(this.state,e)};var _r=w.__b;w.__b=function(t){t.type&&t.type.__f&&t.ref&&(t.props.ref=t.ref,t.ref=null),_r&&_r(t)};var Io=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.forward_ref")||3911,Or=function(t,e){return t==null?null:$($(t).map(e))},Do={map:Or,forEach:Or,count:function(t){return t?$(t).length:0},only:function(t){var e=$(t);if(e.length!==1)throw"Children.only";return e[0]},toArray:$},ko=w.__e;function ct(){this.__u=0,this.t=null,this.__b=null}function Tn(t){var e=t.__.__c;return e&&e.__e&&e.__e(t)}function je(){this.u=null,this.o=null}w.__e=function(t,e,r){if(t.then){for(var n,o=e;o=o.__;)if((n=o.__c)&&n.__c)return e.__e==null&&(e.__e=r.__e,e.__k=r.__k),n.__c(t,e)}ko(t,e,r)},(ct.prototype=new K).__c=function(t,e){var r=e.__c,n=this;n.t==null&&(n.t=[]),n.t.push(r);var o=Tn(n.__v),i=!1,a=function(){i||(i=!0,r.componentWillUnmount=r.__c,o?o(c):c())};r.__c=r.componentWillUnmount,r.componentWillUnmount=function(){a(),r.__c&&r.__c()};var c=function(){if(!--n.__u){if(n.state.__e){var s=n.state.__e;n.__v.__k[0]=function m(p,v,d){return p&&(p.__v=null,p.__k=p.__k&&p.__k.map(function(h){return m(h,v,d)}),p.__c&&p.__c.__P===v&&(p.__e&&d.insertBefore(p.__e,p.__d),p.__c.__e=!0,p.__c.__P=d)),p}(s,s.__c.__P,s.__c.__O)}var l;for(n.setState({__e:n.__b=null});l=n.t.pop();)l.forceUpdate()}},u=e.__h===!0;n.__u++||u||n.setState({__e:n.__b=n.__v.__k[0]}),t.then(a,a)},ct.prototype.componentWillUnmount=function(){this.t=[]},ct.prototype.render=function(t,e){if(this.__b){if(this.__v.__k){var r=document.createElement("div"),n=this.__v.__k[0].__c;this.__v.__k[0]=function i(a,c,u){return a&&(a.__c&&a.__c.__H&&(a.__c.__H.__.forEach(function(s){typeof s.__c=="function"&&s.__c()}),a.__c.__H=null),(a=Nn({},a)).__c!=null&&(a.__c.__P===u&&(a.__c.__P=c),a.__c=null),a.__k=a.__k&&a.__k.map(function(s){return i(s,c,u)})),a}(this.__b,r,n.__O=n.__P)}this.__b=null}var o=e.__e&&W(X,null,t.fallback);return o&&(o.__h=null),[W(X,null,e.__e?null:t.children),o]};var Sr=function(t,e,r){if(++r[1]===r[0]&&t.o.delete(e),t.props.revealOrder&&(t.props.revealOrder[0]!=="t"||!t.o.size))for(r=t.u;r;){for(;r.length>3;)r.pop()();if(r[1]>>1,1),e.i.removeChild(n)}}),Ke(W(Ao,{context:e.context},t.__v),e.l)):e.l&&e.componentWillUnmount()}function Rn(t,e){return W(Co,{__v:t,i:e})}(je.prototype=new K).__e=function(t){var e=this,r=Tn(e.__v),n=e.o.get(t);return n[0]++,function(o){var i=function(){e.props.revealOrder?(n.push(o),Sr(e,t,n)):o()};r?r(i):i()}},je.prototype.render=function(t){this.u=null,this.o=new Map;var e=$(t.children);t.revealOrder&&t.revealOrder[0]==="b"&&e.reverse();for(var r=e.length;r--;)this.o.set(e[r],this.u=[1,0,this.u]);return t.children},je.prototype.componentDidUpdate=je.prototype.componentDidMount=function(){var t=this;this.o.forEach(function(e,r){Sr(t,r,e)})};var qn=typeof Symbol<"u"&&Symbol.for&&Symbol.for("react.element")||60103,xo=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|fill|flood|font|glyph(?!R)|horiz|marker(?!H|W|U)|overline|paint|stop|strikethrough|stroke|text(?!L)|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,No=function(t){return(typeof Symbol<"u"&&Ve(Symbol())=="symbol"?/fil|che|rad/i:/fil|che|ra/i).test(t)};function Ln(t,e,r){return e.__k==null&&(e.textContent=""),Ke(t,e),typeof r=="function"&&r(),t?t.__c:null}K.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(t){Object.defineProperty(K.prototype,t,{configurable:!0,get:function(){return this["UNSAFE_"+t]},set:function(e){Object.defineProperty(this,t,{configurable:!0,writable:!0,value:e})}})});var wr=w.event;function To(){}function Ro(){return this.cancelBubble}function qo(){return this.defaultPrevented}w.event=function(t){return wr&&(t=wr(t)),t.persist=To,t.isPropagationStopped=Ro,t.isDefaultPrevented=qo,t.nativeEvent=t};var Mn,jr={configurable:!0,get:function(){return this.class}},Er=w.vnode;w.vnode=function(t){var e=t.type,r=t.props,n=r;if(typeof e=="string"){for(var o in n={},r){var i=r[o];o==="value"&&"defaultValue"in r&&i==null||(o==="defaultValue"&&"value"in r&&r.value==null?o="value":o==="download"&&i===!0?i="":/ondoubleclick/i.test(o)?o="ondblclick":/^onchange(textarea|input)/i.test(o+e)&&!No(r.type)?o="oninput":/^on(Ani|Tra|Tou|BeforeInp)/.test(o)?o=o.toLowerCase():xo.test(o)?o=o.replace(/[A-Z0-9]/,"-$&").toLowerCase():i===null&&(i=void 0),n[o]=i)}e=="select"&&n.multiple&&Array.isArray(n.value)&&(n.value=$(r.children).forEach(function(a){a.props.selected=n.value.indexOf(a.props.value)!=-1})),e=="select"&&n.defaultValue!=null&&(n.value=$(r.children).forEach(function(a){a.props.selected=n.multiple?n.defaultValue.indexOf(a.props.value)!=-1:n.defaultValue==a.props.value})),t.props=n}e&&r.class!=r.className&&(jr.enumerable="className"in r,r.className!=null&&(n.class=r.className),Object.defineProperty(n,"className",jr)),t.$$typeof=qn,Er&&Er(t)};var Pr=w.__r;w.__r=function(t){Pr&&Pr(t),Mn=t.__c};var Lo={ReactCurrentDispatcher:{current:{readContext:function(t){return Mn.__n[t.__c].props.value}}}};(typeof performance>"u"?"undefined":Ve(performance))=="object"&&typeof performance.now=="function"&&performance.now.bind(performance);function Ir(t){return!!t&&t.$$typeof===qn}var f={useState:kn,useReducer:An,useEffect:Cn,useLayoutEffect:gr,useRef:function(t){return pe=5,Pt(function(){return{current:t}},[])},useImperativeHandle:function(t,e,r){pe=6,gr(function(){typeof t=="function"?t(e()):t&&(t.current=e())},r==null?r:r.concat(t))},useMemo:Pt,useCallback:function(t,e){return pe=8,Pt(function(){return t},e)},useContext:function(t){var e=q.context[t.__c],r=Je(de++,9);return r.__c=t,e?(r.__==null&&(r.__=!0,e.sub(q)),e.props.value):t.__},useDebugValue:function(t,e){w.useDebugValue&&w.useDebugValue(e?e(t):t)},version:"16.8.0",Children:Do,render:Ln,hydrate:function(t,e,r){return Dn(t,e),typeof r=="function"&&r(),t?t.__c:null},unmountComponentAtNode:function(t){return!!t.__k&&(Ke(null,t),!0)},createPortal:Rn,createElement:W,createContext:function(t,e){var r={__c:e="__cC"+bn++,__:t,Consumer:function(n,o){return n.children(o)},Provider:function(n){var o,i;return this.getChildContext||(o=[],(i={})[e]=this,this.getChildContext=function(){return i},this.shouldComponentUpdate=function(a){this.props.value!==a.value&&o.some(Mt)},this.sub=function(a){o.push(a);var c=a.componentWillUnmount;a.componentWillUnmount=function(){o.splice(o.indexOf(a),1),c&&c.call(a)}}),n.children}};return r.Provider.__=r.Consumer.contextType=r},createFactory:function(t){return W.bind(null,t)},cloneElement:function(t){return Ir(t)?Eo.apply(null,arguments):t},createRef:function(){return{current:null}},Fragment:X,isValidElement:Ir,findDOMNode:function(t){return t&&(t.base||t.nodeType===1&&t)||null},Component:K,PureComponent:Bt,memo:function(t,e){function r(o){var i=this.props.ref,a=i==o.ref;return!a&&i&&(i.call?i(null):i.current=null),e?!e(this.props,o)||!a:Ft(this.props,o)}function n(o){return this.shouldComponentUpdate=r,W(t,o)}return n.displayName="Memo("+(t.displayName||t.name)+")",n.prototype.isReactComponent=!0,n.__f=!0,n},forwardRef:function(t){function e(r,n){var o=Nn({},r);return delete o.ref,t(o,(n=r.ref||n)&&(Ve(n)!="object"||"current"in n)?n:null)}return e.$$typeof=Io,e.render=e,e.prototype.isReactComponent=e.__f=!0,e.displayName="ForwardRef("+(t.displayName||t.name)+")",e},unstable_batchedUpdates:function(t,e){return t(e)},StrictMode:X,Suspense:ct,SuspenseList:je,lazy:function(t){var e,r,n;function o(i){if(e||(e=t()).then(function(a){r=a.default||a},function(a){n=a}),n)throw n;if(!r)throw e;return W(r,i)}return o.displayName="Lazy",o.__f=!0,o},__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:Lo};function Mo(){return f.createElement("svg",{width:"15",height:"15",className:"DocSearch-Control-Key-Icon"},f.createElement("path",{d:"M4.505 4.496h2M5.505 5.496v5M8.216 4.496l.055 5.993M10 7.5c.333.333.5.667.5 1v2M12.326 4.5v5.996M8.384 4.496c1.674 0 2.116 0 2.116 1.5s-.442 1.5-2.116 1.5M3.205 9.303c-.09.448-.277 1.21-1.241 1.203C1 10.5.5 9.513.5 8V7c0-1.57.5-2.5 1.464-2.494.964.006 1.134.598 1.24 1.342M12.553 10.5h1.953",strokeWidth:"1.2",stroke:"currentColor",fill:"none",strokeLinecap:"square"}))}function Hn(){return f.createElement("svg",{width:"20",height:"20",className:"DocSearch-Search-Icon",viewBox:"0 0 20 20"},f.createElement("path",{d:"M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"}))}var Ho=["translations"];function Vt(){return Vt=Object.assign||function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=new Array(e);r=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}var Bo=f.forwardRef(function(t,e){var r=t.translations,n=r===void 0?{}:r,o=Fo(t,Ho),i=n.buttonText,a=i===void 0?"Search":i,c=n.buttonAriaLabel,u=c===void 0?"Search":c,s=Uo(kn(null),2),l=s[0],m=s[1];return Cn(function(){typeof navigator<"u"&&(/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)?m("⌘"):m("Ctrl"))},[]),f.createElement("button",Vt({type:"button",className:"DocSearch DocSearch-Button","aria-label":u},o,{ref:e}),f.createElement("span",{className:"DocSearch-Button-Container"},f.createElement(Hn,null),f.createElement("span",{className:"DocSearch-Button-Placeholder"},a)),f.createElement("span",{className:"DocSearch-Button-Keys"},l!==null&&f.createElement(f.Fragment,null,f.createElement("kbd",{className:"DocSearch-Button-Key"},l==="Ctrl"?f.createElement(Mo,null):l),f.createElement("kbd",{className:"DocSearch-Button-Key"},"K"))))});function Un(t,e){var r=void 0;return function(){for(var n=arguments.length,o=new Array(n),i=0;it.length)&&(e=t.length);for(var r=0,n=new Array(e);rt.length)&&(e=t.length);for(var r=0,n=new Array(e);r=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function xr(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),r.push.apply(r,n)}return r}function ve(t){for(var e=1;e1&&arguments[1]!==void 0?arguments[1]:20,r=[],n=0;n=3||r===2&&n>=4||r===1&&n>=10);function i(a,c,u){if(o&&u!==void 0){var s=u[0].__autocomplete_algoliaCredentials,l={"X-Algolia-Application-Id":s.appId,"X-Algolia-API-Key":s.apiKey};t.apply(void 0,[a].concat(Ge(c),[{headers:l}]))}else t.apply(void 0,[a].concat(Ge(c)))}return{init:function(a,c){t("init",{appId:a,apiKey:c})},setUserToken:function(a){t("setUserToken",a)},clickedObjectIDsAfterSearch:function(){for(var a=arguments.length,c=new Array(a),u=0;u0&&i("clickedObjectIDsAfterSearch",Xe(c),c[0].items)},clickedObjectIDs:function(){for(var a=arguments.length,c=new Array(a),u=0;u0&&i("clickedObjectIDs",Xe(c),c[0].items)},clickedFilters:function(){for(var a=arguments.length,c=new Array(a),u=0;u0&&t.apply(void 0,["clickedFilters"].concat(c))},convertedObjectIDsAfterSearch:function(){for(var a=arguments.length,c=new Array(a),u=0;u0&&i("convertedObjectIDsAfterSearch",Xe(c),c[0].items)},convertedObjectIDs:function(){for(var a=arguments.length,c=new Array(a),u=0;u0&&i("convertedObjectIDs",Xe(c),c[0].items)},convertedFilters:function(){for(var a=arguments.length,c=new Array(a),u=0;u0&&t.apply(void 0,["convertedFilters"].concat(c))},viewedObjectIDs:function(){for(var a=arguments.length,c=new Array(a),u=0;u0&&c.reduce(function(s,l){var m=l.items,p=Bn(l,Jo);return[].concat(Ge(s),Ge(Qo(ve(ve({},p),{},{objectIDs:(m==null?void 0:m.map(function(v){return v.objectID}))||p.objectIDs})).map(function(v){return{items:m,payload:v}})))},[]).forEach(function(s){var l=s.items;return i("viewedObjectIDs",[s.payload],l)})},viewedFilters:function(){for(var a=arguments.length,c=new Array(a),u=0;u0&&t.apply(void 0,["viewedFilters"].concat(c))}}}function Yo(t){var e=t.items.reduce(function(r,n){var o;return r[n.__autocomplete_indexName]=((o=r[n.__autocomplete_indexName])!==null&&o!==void 0?o:[]).concat(n),r},{});return Object.keys(e).map(function(r){return{index:r,items:e[r],algoliaSource:["autocomplete"]}})}function Dt(t){return t.objectID&&t.__autocomplete_indexName&&t.__autocomplete_queryID}function ke(t){return ke=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},ke(t)}function ie(t){return function(e){if(Array.isArray(e))return kt(e)}(t)||function(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}(t)||function(e,r){if(e){if(typeof e=="string")return kt(e,r);var n=Object.prototype.toString.call(e).slice(8,-1);if(n==="Object"&&e.constructor&&(n=e.constructor.name),n==="Map"||n==="Set")return Array.from(e);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return kt(e,r)}}(t)||function(){throw new TypeError(`Invalid attempt to spread non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}function kt(t,e){(e==null||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r0&&ei({onItemsChange:n,items:p,insights:c,state:m}))}},0);return{name:"aa.algoliaInsightsPlugin",subscribe:function(l){var m=l.setContext,p=l.onSelect,v=l.onActive;a("addAlgoliaAgent","insights-plugin"),m({algoliaInsightsPlugin:{__algoliaSearchParameters:{clickAnalytics:!0},insights:c}}),p(function(d){var h=d.item,y=d.state,b=d.event;Dt(h)&&o({state:y,event:b,insights:c,item:h,insightsEvents:[G({eventName:"Item Selected"},Ar({item:h,items:u.current}))]})}),v(function(d){var h=d.item,y=d.state,b=d.event;Dt(h)&&i({state:y,event:b,insights:c,item:h,insightsEvents:[G({eventName:"Item Active"},Ar({item:h,items:u.current}))]})})},onStateChange:function(l){var m=l.state;s({state:m})},__autocomplete_pluginOptions:t}}function lt(t,e){var r=e;return{then:function(n,o){return lt(t.then(et(n,r,t),et(o,r,t)),r)},catch:function(n){return lt(t.catch(et(n,r,t)),r)},finally:function(n){return n&&r.onCancelList.push(n),lt(t.finally(et(n&&function(){return r.onCancelList=[],n()},r,t)),r)},cancel:function(){r.isCanceled=!0;var n=r.onCancelList;r.onCancelList=[],n.forEach(function(o){o()})},isCanceled:function(){return r.isCanceled===!0}}}function Tr(t){return lt(t,{isCanceled:!1,onCancelList:[]})}function et(t,e,r){return t?function(n){return e.isCanceled?n:t(n)}:r}function Rr(t,e,r,n){if(!r)return null;if(t<0&&(e===null||n!==null&&e===0))return r+t;var o=(e===null?-1:e)+t;return o<=-1||o>=r?n===null?null:0:o}function qr(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),r.push.apply(r,n)}return r}function Lr(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=new Array(e);r0},reshape:function(i){return i.sources}},t),{},{id:(r=t.id)!==null&&r!==void 0?r:"autocomplete-".concat(Vo++),plugins:o,initialState:ae({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},t.initialState),onStateChange:function(i){var a;(a=t.onStateChange)===null||a===void 0||a.call(t,i),o.forEach(function(c){var u;return(u=c.onStateChange)===null||u===void 0?void 0:u.call(c,i)})},onSubmit:function(i){var a;(a=t.onSubmit)===null||a===void 0||a.call(t,i),o.forEach(function(c){var u;return(u=c.onSubmit)===null||u===void 0?void 0:u.call(c,i)})},onReset:function(i){var a;(a=t.onReset)===null||a===void 0||a.call(t,i),o.forEach(function(c){var u;return(u=c.onReset)===null||u===void 0?void 0:u.call(c,i)})},getSources:function(i){return Promise.all([].concat(ui(o.map(function(a){return a.getSources})),[t.getSources]).filter(Boolean).map(function(a){return function(c,u){var s=[];return Promise.resolve(c(u)).then(function(l){return Promise.all(l.filter(function(m){return!!m}).map(function(m){if(m.sourceId,s.includes(m.sourceId))throw new Error("[Autocomplete] The `sourceId` ".concat(JSON.stringify(m.sourceId)," is not unique."));s.push(m.sourceId);var p={getItemInputValue:function(d){return d.state.query},getItemUrl:function(){},onSelect:function(d){(0,d.setIsOpen)(!1)},onActive:vt,onResolve:vt};Object.keys(p).forEach(function(d){p[d].__default=!0});var v=Lr(Lr({},p),m);return Promise.resolve(v)}))})}(a,i)})).then(function(a){return ze(a)}).then(function(a){return a.map(function(c){return ae(ae({},c),{},{onSelect:function(u){c.onSelect(u),e.forEach(function(s){var l;return(l=s.onSelect)===null||l===void 0?void 0:l.call(s,u)})},onActive:function(u){c.onActive(u),e.forEach(function(s){var l;return(l=s.onActive)===null||l===void 0?void 0:l.call(s,u)})},onResolve:function(u){c.onResolve(u),e.forEach(function(s){var l;return(l=s.onResolve)===null||l===void 0?void 0:l.call(s,u)})}})})})},navigator:ae({navigate:function(i){var a=i.itemUrl;n.location.assign(a)},navigateNewTab:function(i){var a=i.itemUrl,c=n.open(a,"_blank","noopener");c==null||c.focus()},navigateNewWindow:function(i){var a=i.itemUrl;n.open(a,"_blank","noopener")}},t.navigator)})}function Te(t){return Te=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Te(t)}function Fr(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),r.push.apply(r,n)}return r}function rt(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=new Array(e);r=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}var Wr,xt,ot,we=null,Kr=(Wr=-1,xt=-1,ot=void 0,function(t){var e=++Wr;return Promise.resolve(t).then(function(r){return ot&&e=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function Me(t){return Me=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Me(t)}var _i=["props","refresh","store"],Oi=["inputElement","formElement","panelElement"],Si=["inputElement"],wi=["inputElement","maxLength"],ji=["sourceIndex"],Ei=["sourceIndex"],Pi=["item","source","sourceIndex"];function Jr(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),r.push.apply(r,n)}return r}function R(t){for(var e=1;e=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function Di(t){var e=t.props,r=t.refresh,n=t.store,o=re(t,_i),i=function(a,c){return c!==void 0?"".concat(a,"-").concat(c):a};return{getEnvironmentProps:function(a){var c=a.inputElement,u=a.formElement,s=a.panelElement;function l(m){!n.getState().isOpen&&n.pendingRequests.isEmpty()||m.target===c||[u,s].some(function(p){return v=p,d=m.target,v===d||v.contains(d);var v,d})===!1&&(n.dispatch("blur",null),e.debug||n.pendingRequests.cancelAll())}return R({onTouchStart:l,onMouseDown:l,onTouchMove:function(m){n.getState().isOpen!==!1&&c===e.environment.document.activeElement&&m.target!==c&&c.blur()}},re(a,Oi))},getRootProps:function(a){return R({role:"combobox","aria-expanded":n.getState().isOpen,"aria-haspopup":"listbox","aria-owns":n.getState().isOpen?"".concat(e.id,"-list"):void 0,"aria-labelledby":"".concat(e.id,"-label")},a)},getFormProps:function(a){return a.inputElement,R({action:"",noValidate:!0,role:"search",onSubmit:function(c){var u;c.preventDefault(),e.onSubmit(R({event:c,refresh:r,state:n.getState()},o)),n.dispatch("submit",null),(u=a.inputElement)===null||u===void 0||u.blur()},onReset:function(c){var u;c.preventDefault(),e.onReset(R({event:c,refresh:r,state:n.getState()},o)),n.dispatch("reset",null),(u=a.inputElement)===null||u===void 0||u.focus()}},re(a,Si))},getLabelProps:function(a){var c=a||{},u=c.sourceIndex,s=re(c,ji);return R({htmlFor:"".concat(i(e.id,u),"-input"),id:"".concat(i(e.id,u),"-label")},s)},getInputProps:function(a){var c;function u(y){(e.openOnFocus||n.getState().query)&&le(R({event:y,props:e,query:n.getState().completion||n.getState().query,refresh:r,store:n},o)),n.dispatch("focus",null)}var s=a||{},l=(s.inputElement,s.maxLength),m=l===void 0?512:l,p=re(s,wi),v=fe(n.getState()),d=function(y){return!!(y&&y.match(ni))}(((c=e.environment.navigator)===null||c===void 0?void 0:c.userAgent)||""),h=v!=null&&v.itemUrl&&!d?"go":"search";return R({"aria-autocomplete":"both","aria-activedescendant":n.getState().isOpen&&n.getState().activeItemId!==null?"".concat(e.id,"-item-").concat(n.getState().activeItemId):void 0,"aria-controls":n.getState().isOpen?"".concat(e.id,"-list"):void 0,"aria-labelledby":"".concat(e.id,"-label"),value:n.getState().completion||n.getState().query,id:"".concat(e.id,"-input"),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",enterKeyHint:h,spellCheck:"false",autoFocus:e.autoFocus,placeholder:e.placeholder,maxLength:m,type:"search",onChange:function(y){le(R({event:y,props:e,query:y.currentTarget.value.slice(0,m),refresh:r,store:n},o))},onKeyDown:function(y){(function(b){var _=b.event,S=b.props,O=b.refresh,g=b.store,P=bi(b,yi);if(_.key==="ArrowUp"||_.key==="ArrowDown"){var C=function(){var M=S.environment.document.getElementById("".concat(S.id,"-item-").concat(g.getState().activeItemId));M&&(M.scrollIntoViewIfNeeded?M.scrollIntoViewIfNeeded(!1):M.scrollIntoView(!1))},L=function(){var M=fe(g.getState());if(g.getState().activeItemId!==null&&M){var Ot=M.item,St=M.itemInputValue,$e=M.itemUrl,B=M.source;B.onActive(te({event:_,item:Ot,itemInputValue:St,itemUrl:$e,refresh:O,source:B,state:g.getState()},P))}};_.preventDefault(),g.getState().isOpen===!1&&(S.openOnFocus||g.getState().query)?le(te({event:_,props:S,query:g.getState().query,refresh:O,store:g},P)).then(function(){g.dispatch(_.key,{nextActiveItemId:S.defaultActiveItemId}),L(),setTimeout(C,0)}):(g.dispatch(_.key,{}),L(),C())}else if(_.key==="Escape")_.preventDefault(),g.dispatch(_.key,null),g.pendingRequests.cancelAll();else if(_.key==="Tab")g.dispatch("blur",null),g.pendingRequests.cancelAll();else if(_.key==="Enter"){if(g.getState().activeItemId===null||g.getState().collections.every(function(M){return M.items.length===0}))return void(S.debug||g.pendingRequests.cancelAll());_.preventDefault();var x=fe(g.getState()),k=x.item,N=x.itemInputValue,U=x.itemUrl,F=x.source;if(_.metaKey||_.ctrlKey)U!==void 0&&(F.onSelect(te({event:_,item:k,itemInputValue:N,itemUrl:U,refresh:O,source:F,state:g.getState()},P)),S.navigator.navigateNewTab({itemUrl:U,item:k,state:g.getState()}));else if(_.shiftKey)U!==void 0&&(F.onSelect(te({event:_,item:k,itemInputValue:N,itemUrl:U,refresh:O,source:F,state:g.getState()},P)),S.navigator.navigateNewWindow({itemUrl:U,item:k,state:g.getState()}));else if(!_.altKey){if(U!==void 0)return F.onSelect(te({event:_,item:k,itemInputValue:N,itemUrl:U,refresh:O,source:F,state:g.getState()},P)),void S.navigator.navigate({itemUrl:U,item:k,state:g.getState()});le(te({event:_,nextState:{isOpen:!1},props:S,query:N,refresh:O,store:g},P)).then(function(){F.onSelect(te({event:_,item:k,itemInputValue:N,itemUrl:U,refresh:O,source:F,state:g.getState()},P))})}}})(R({event:y,props:e,refresh:r,store:n},o))},onFocus:u,onBlur:vt,onClick:function(y){a.inputElement!==e.environment.document.activeElement||n.getState().isOpen||u(y)}},p)},getPanelProps:function(a){return R({onMouseDown:function(c){c.preventDefault()},onMouseLeave:function(){n.dispatch("mouseleave",null)}},a)},getListProps:function(a){var c=a||{},u=c.sourceIndex,s=re(c,Ei);return R({role:"listbox","aria-labelledby":"".concat(i(e.id,u),"-label"),id:"".concat(i(e.id,u),"-list")},s)},getItemProps:function(a){var c=a.item,u=a.source,s=a.sourceIndex,l=re(a,Pi);return R({id:"".concat(i(e.id,s),"-item-").concat(c.__autocomplete_id),role:"option","aria-selected":n.getState().activeItemId===c.__autocomplete_id,onMouseMove:function(m){if(c.__autocomplete_id!==n.getState().activeItemId){n.dispatch("mousemove",c.__autocomplete_id);var p=fe(n.getState());if(n.getState().activeItemId!==null&&p){var v=p.item,d=p.itemInputValue,h=p.itemUrl,y=p.source;y.onActive(R({event:m,item:v,itemInputValue:d,itemUrl:h,refresh:r,source:y,state:n.getState()},o))}}},onMouseDown:function(m){m.preventDefault()},onClick:function(m){var p=u.getItemInputValue({item:c,state:n.getState()}),v=u.getItemUrl({item:c,state:n.getState()});(v?Promise.resolve():le(R({event:m,nextState:{isOpen:!1},props:e,query:p,refresh:r,store:n},o))).then(function(){u.onSelect(R({event:m,item:c,itemInputValue:p,itemUrl:v,refresh:r,source:u,state:n.getState()},o))})}},l)}}}function He(t){return He=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},He(t)}function $r(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),r.push.apply(r,n)}return r}function ki(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=new Array(e);r=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function Yi(t){var e=t.translations,r=e===void 0?{}:e,n=Zi(t,$i),o=r.noResultsText,i=o===void 0?"No results for":o,a=r.suggestedQueryText,c=a===void 0?"Try searching for":a,u=r.reportMissingResultsText,s=u===void 0?"Believe this query should return results?":u,l=r.reportMissingResultsLinkText,m=l===void 0?"Let us know.":l,p=n.state.context.searchSuggestions;return f.createElement("div",{className:"DocSearch-NoResults"},f.createElement("div",{className:"DocSearch-Screen-Icon"},f.createElement(zi,null)),f.createElement("p",{className:"DocSearch-Title"},i,' "',f.createElement("strong",null,n.state.query),'"'),p&&p.length>0&&f.createElement("div",{className:"DocSearch-NoResults-Prefill-List"},f.createElement("p",{className:"DocSearch-Help"},c,":"),f.createElement("ul",null,p.slice(0,3).reduce(function(v,d){return[].concat(Qi(v),[f.createElement("li",{key:d},f.createElement("button",{className:"DocSearch-Prefill",key:d,type:"button",onClick:function(){n.setQuery(d.toLowerCase()+" "),n.refresh(),n.inputRef.current.focus()}},d))])},[]))),n.getMissingResultsUrl&&f.createElement("p",{className:"DocSearch-Help"},"".concat(s," "),f.createElement("a",{href:n.getMissingResultsUrl({query:n.state.query}),target:"_blank",rel:"noopener noreferrer"},m)))}var Gi=["hit","attribute","tagName"];function Xr(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(o){return Object.getOwnPropertyDescriptor(t,o).enumerable})),r.push.apply(r,n)}return r}function en(t){for(var e=1;e=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function tn(t,e){return e.split(".").reduce(function(r,n){return r!=null&&r[n]?r[n]:null},t)}function ce(t){var e=t.hit,r=t.attribute,n=t.tagName;return W(n===void 0?"span":n,en(en({},ea(t,Gi)),{},{dangerouslySetInnerHTML:{__html:tn(e,"_snippetResult.".concat(r,".value"))||tn(e,r)}}))}function rn(t,e){return function(r){if(Array.isArray(r))return r}(t)||function(r,n){var o=r==null?null:typeof Symbol<"u"&&r[Symbol.iterator]||r["@@iterator"];if(o!=null){var i,a,c=[],u=!0,s=!1;try{for(o=o.call(r);!(u=(i=o.next()).done)&&(c.push(i.value),!n||c.length!==n);u=!0);}catch(l){s=!0,a=l}finally{try{u||o.return==null||o.return()}finally{if(s)throw a}}return c}}(t,e)||function(r,n){if(r){if(typeof r=="string")return nn(r,n);var o=Object.prototype.toString.call(r).slice(8,-1);if(o==="Object"&&r.constructor&&(o=r.constructor.name),o==="Map"||o==="Set")return Array.from(r);if(o==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(o))return nn(r,n)}}(t,e)||function(){throw new TypeError(`Invalid attempt to destructure non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}()}function nn(t,e){(e==null||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r|<\/mark>)/g,na=RegExp(zn.source);function Jn(t){var e,r,n=t;if(!n.__docsearch_parent&&!t._highlightResult)return t.hierarchy.lvl0;var o=((n.__docsearch_parent?(e=n.__docsearch_parent)===null||e===void 0||(e=e._highlightResult)===null||e===void 0||(e=e.hierarchy)===null||e===void 0?void 0:e.lvl0:(r=t._highlightResult)===null||r===void 0||(r=r.hierarchy)===null||r===void 0?void 0:r.lvl0)||{}).value;return o&&na.test(o)?o.replace(zn,""):o}function Jt(){return Jt=Object.assign||function(t){for(var e=1;e=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function ua(t){var e=t.translations,r=e===void 0?{}:e,n=aa(t,ia),o=r.recentSearchesTitle,i=o===void 0?"Recent":o,a=r.noRecentSearchesText,c=a===void 0?"No recent searches":a,u=r.saveRecentSearchButtonTitle,s=u===void 0?"Save this search":u,l=r.removeRecentSearchButtonTitle,m=l===void 0?"Remove this search from history":l,p=r.favoriteSearchesTitle,v=p===void 0?"Favorite":p,d=r.removeFavoriteSearchButtonTitle,h=d===void 0?"Remove this search from favorites":d;return n.state.status==="idle"&&n.hasCollections===!1?n.disableUserPersonalization?null:f.createElement("div",{className:"DocSearch-StartScreen"},f.createElement("p",{className:"DocSearch-Help"},c)):n.hasCollections===!1?null:f.createElement("div",{className:"DocSearch-Dropdown-Container"},f.createElement(zt,ht({},n,{title:i,collection:n.state.collections[0],renderIcon:function(){return f.createElement("div",{className:"DocSearch-Hit-icon"},f.createElement(Hi,null))},renderAction:function(y){var b=y.item,_=y.runFavoriteTransition,S=y.runDeleteTransition;return f.createElement(f.Fragment,null,f.createElement("div",{className:"DocSearch-Hit-action"},f.createElement("button",{className:"DocSearch-Hit-action-button",title:s,type:"submit",onClick:function(O){O.preventDefault(),O.stopPropagation(),_(function(){n.favoriteSearches.add(b),n.recentSearches.remove(b),n.refresh()})}},f.createElement(Gr,null))),f.createElement("div",{className:"DocSearch-Hit-action"},f.createElement("button",{className:"DocSearch-Hit-action-button",title:m,type:"submit",onClick:function(O){O.preventDefault(),O.stopPropagation(),S(function(){n.recentSearches.remove(b),n.refresh()})}},f.createElement(Kt,null))))}})),f.createElement(zt,ht({},n,{title:v,collection:n.state.collections[1],renderIcon:function(){return f.createElement("div",{className:"DocSearch-Hit-icon"},f.createElement(Gr,null))},renderAction:function(y){var b=y.item,_=y.runDeleteTransition;return f.createElement("div",{className:"DocSearch-Hit-action"},f.createElement("button",{className:"DocSearch-Hit-action-button",title:h,type:"submit",onClick:function(S){S.preventDefault(),S.stopPropagation(),_(function(){n.favoriteSearches.remove(b),n.refresh()})}},f.createElement(Kt,null)))}})))}var ca=["translations"];function yt(){return yt=Object.assign||function(t){for(var e=1;e=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}var sa=f.memo(function(t){var e=t.translations,r=e===void 0?{}:e,n=la(t,ca);if(n.state.status==="error")return f.createElement(Ji,{translations:r==null?void 0:r.errorScreen});var o=n.state.collections.some(function(i){return i.items.length>0});return n.state.query?o===!1?f.createElement(Yi,yt({},n,{translations:r==null?void 0:r.noResultsScreen})):f.createElement(oa,n):f.createElement(ua,yt({},n,{hasCollections:o,translations:r==null?void 0:r.startScreen}))},function(t,e){return e.state.status==="loading"||e.state.status==="stalled"}),fa=["translations"];function gt(){return gt=Object.assign||function(t){for(var e=1;e=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function pa(t){var e=t.translations,r=e===void 0?{}:e,n=ma(t,fa),o=r.resetButtonTitle,i=o===void 0?"Clear the query":o,a=r.resetButtonAriaLabel,c=a===void 0?"Clear the query":a,u=r.cancelButtonText,s=u===void 0?"Cancel":u,l=r.cancelButtonAriaLabel,m=l===void 0?"Cancel":l,p=n.getFormProps({inputElement:n.inputRef.current}).onReset;return f.useEffect(function(){n.autoFocus&&n.inputRef.current&&n.inputRef.current.focus()},[n.autoFocus,n.inputRef]),f.useEffect(function(){n.isFromSelection&&n.inputRef.current&&n.inputRef.current.select()},[n.isFromSelection,n.inputRef]),f.createElement(f.Fragment,null,f.createElement("form",{className:"DocSearch-Form",onSubmit:function(v){v.preventDefault()},onReset:p},f.createElement("label",gt({className:"DocSearch-MagnifierLabel"},n.getLabelProps()),f.createElement(Hn,null)),f.createElement("div",{className:"DocSearch-LoadingIndicator"},f.createElement(Mi,null)),f.createElement("input",gt({className:"DocSearch-Input",ref:n.inputRef},n.getInputProps({inputElement:n.inputRef.current,autoFocus:n.autoFocus,maxLength:64}))),f.createElement("button",{type:"reset",title:i,className:"DocSearch-Reset","aria-label":c,hidden:!n.state.query},f.createElement(Kt,null))),f.createElement("button",{className:"DocSearch-Cancel",type:"reset","aria-label":m,onClick:n.onClose},s))}var va=["_highlightResult","_snippetResult"];function da(t,e){if(t==null)return{};var r,n,o=function(a,c){if(a==null)return{};var u,s,l={},m=Object.keys(a);for(s=0;s=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function ha(t){return function(){var e="__TEST_KEY__";try{return localStorage.setItem(e,""),localStorage.removeItem(e),!0}catch{return!1}}()===!1?{setItem:function(){},getItem:function(){return[]}}:{setItem:function(e){return window.localStorage.setItem(t,JSON.stringify(e))},getItem:function(){var e=window.localStorage.getItem(t);return e?JSON.parse(e):[]}}}function un(t){var e=t.key,r=t.limit,n=r===void 0?5:r,o=ha(e),i=o.getItem().slice(0,n);return{add:function(a){var c=a,u=(c._highlightResult,c._snippetResult,da(c,va)),s=i.findIndex(function(l){return l.objectID===u.objectID});s>-1&&i.splice(s,1),i.unshift(u),i=i.slice(0,n),o.setItem(i)},remove:function(a){i=i.filter(function(c){return c.objectID!==a.objectID}),o.setItem(i)},getAll:function(){return i}}}var ya=["facetName","facetQuery"];function ga(t){var e,r="algoliasearch-client-js-".concat(t.key),n=function(){return e===void 0&&(e=t.localStorage||window.localStorage),e},o=function(){return JSON.parse(n().getItem(r)||"{}")},i=function(c){n().setItem(r,JSON.stringify(c))},a=function(){var c=t.timeToLive?1e3*t.timeToLive:null,u=o(),s=Object.fromEntries(Object.entries(u).filter(function(m){return se(m,2)[1].timestamp!==void 0}));if(i(s),c){var l=Object.fromEntries(Object.entries(s).filter(function(m){var p=se(m,2)[1],v=new Date().getTime();return!(p.timestamp+c2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}};return Promise.resolve().then(function(){a();var l=JSON.stringify(c);return o()[l]}).then(function(l){return Promise.all([l?l.value:u(),l!==void 0])}).then(function(l){var m=se(l,2),p=m[0],v=m[1];return Promise.all([p,v||s.miss(p)])}).then(function(l){return se(l,1)[0]})},set:function(c,u){return Promise.resolve().then(function(){var s=o();return s[JSON.stringify(c)]={timestamp:new Date().getTime(),value:u},n().setItem(r,JSON.stringify(s)),u})},delete:function(c){return Promise.resolve().then(function(){var u=o();delete u[JSON.stringify(c)],n().setItem(r,JSON.stringify(u))})},clear:function(){return Promise.resolve().then(function(){n().removeItem(r)})}}}function Ee(t){var e=ft(t.caches),r=e.shift();return r===void 0?{get:function(n,o){var i=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}};return o().then(function(a){return Promise.all([a,i.miss(a)])}).then(function(a){return se(a,1)[0]})},set:function(n,o){return Promise.resolve(o)},delete:function(n){return Promise.resolve()},clear:function(){return Promise.resolve()}}:{get:function(n,o){var i=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}};return r.get(n,o,i).catch(function(){return Ee({caches:e}).get(n,o,i)})},set:function(n,o){return r.set(n,o).catch(function(){return Ee({caches:e}).set(n,o)})},delete:function(n){return r.delete(n).catch(function(){return Ee({caches:e}).delete(n)})},clear:function(){return r.clear().catch(function(){return Ee({caches:e}).clear()})}}}function Tt(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{serializable:!0},e={};return{get:function(r,n){var o=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{miss:function(){return Promise.resolve()}},i=JSON.stringify(r);if(i in e)return Promise.resolve(t.serializable?JSON.parse(e[i]):e[i]);var a=n(),c=o&&o.miss||function(){return Promise.resolve()};return a.then(function(u){return c(u)}).then(function(){return a})},set:function(r,n){return e[JSON.stringify(r)]=t.serializable?JSON.stringify(n):n,Promise.resolve(n)},delete:function(r){return delete e[JSON.stringify(r)],Promise.resolve()},clear:function(){return e={},Promise.resolve()}}}function ba(t){for(var e=t.length-1;e>0;e--){var r=Math.floor(Math.random()*(e+1)),n=t[e];t[e]=t[r],t[r]=n}return t}function $n(t,e){return e&&Object.keys(e).forEach(function(r){t[r]=e[r](t)}),t}function bt(t){for(var e=arguments.length,r=new Array(e>1?e-1:0),n=1;n0?n:void 0,timeout:r.timeout||e,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var me={Read:1,Write:2,Any:3},Qn=1,_a=2,Zn=3;function Yn(t){var e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:Qn;return I(I({},t),{},{status:e,lastUpdate:Date.now()})}function Gn(t){return typeof t=="string"?{protocol:"https",url:t,accept:me.Any}:{protocol:t.protocol||"https",url:t.url,accept:t.accept||me.Any}}var $t="GET",_t="POST";function Oa(t,e){return Promise.all(e.map(function(r){return t.get(r,function(){return Promise.resolve(Yn(r))})})).then(function(r){var n=r.filter(function(a){return function(c){return c.status===Qn||Date.now()-c.lastUpdate>12e4}(a)}),o=r.filter(function(a){return function(c){return c.status===Zn&&Date.now()-c.lastUpdate<=12e4}(a)}),i=[].concat(ft(n),ft(o));return{getTimeout:function(a,c){return(o.length===0&&a===0?1:o.length+3+a)*c},statelessHosts:i.length>0?i.map(function(a){return Gn(a)}):e}})}function ln(t,e,r,n){var o=[],i=function(p,v){if(!(p.method===$t||p.data===void 0&&v.data===void 0)){var d=Array.isArray(p.data)?p.data:I(I({},p.data),v.data);return JSON.stringify(d)}}(r,n),a=function(p,v){var d=I(I({},p.headers),v.headers),h={};return Object.keys(d).forEach(function(y){var b=d[y];h[y.toLowerCase()]=b}),h}(t,n),c=r.method,u=r.method!==$t?{}:I(I({},r.data),n.data),s=I(I(I({"x-algolia-agent":t.userAgent.value},t.queryParameters),u),n.queryParameters),l=0,m=function p(v,d){var h=v.pop();if(h===void 0)throw{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:sn(o)};var y={data:i,headers:a,method:c,url:wa(h,r.path,s),connectTimeout:d(l,t.timeouts.connect),responseTimeout:d(l,n.timeout)},b=function(S){var O={request:y,response:S,host:h,triesLeft:v.length};return o.push(O),O},_={onSuccess:function(S){return function(O){try{return JSON.parse(O.content)}catch(g){throw function(P,C){return{name:"DeserializationError",message:P,response:C}}(g.message,O)}}(S)},onRetry:function(S){var O=b(S);return S.isTimedOut&&l++,Promise.all([t.logger.info("Retryable failure",eo(O)),t.hostsCache.set(h,Yn(h,S.isTimedOut?Zn:_a))]).then(function(){return p(v,d)})},onFail:function(S){throw b(S),function(O,g){var P=O.content,C=O.status,L=P;try{L=JSON.parse(P).message}catch{}return function(x,k,N){return{name:"ApiError",message:x,status:k,transporterStackTrace:N}}(L,C,g)}(S,sn(o))}};return t.requester.send(y).then(function(S){return function(O,g){return function(P){var C=P.status;return P.isTimedOut||function(L){var x=L.isTimedOut,k=L.status;return!x&&~~k==0}(P)||~~(C/100)!=2&&~~(C/100)!=4}(O)?g.onRetry(O):~~(O.status/100)==2?g.onSuccess(O):g.onFail(O)}(S,_)})};return Oa(t.hostsCache,e).then(function(p){return m(ft(p.statelessHosts).reverse(),p.getTimeout)})}function Sa(t){var e={value:"Algolia for JavaScript (".concat(t,")"),add:function(r){var n="; ".concat(r.segment).concat(r.version!==void 0?" (".concat(r.version,")"):"");return e.value.indexOf(n)===-1&&(e.value="".concat(e.value).concat(n)),e}};return e}function wa(t,e,r){var n=Xn(r),o="".concat(t.protocol,"://").concat(t.url,"/").concat(e.charAt(0)==="/"?e.substr(1):e);return n.length&&(o+="?".concat(n)),o}function Xn(t){return Object.keys(t).map(function(e){return bt("%s=%s",e,(r=t[e],Object.prototype.toString.call(r)==="[object Object]"||Object.prototype.toString.call(r)==="[object Array]"?JSON.stringify(t[e]):t[e]));var r}).join("&")}function sn(t){return t.map(function(e){return eo(e)})}function eo(t){var e=t.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return I(I({},t),{},{request:I(I({},t.request),{},{headers:I(I({},t.request.headers),e)})})}var ja=function(t){var e=t.appId,r=function(i,a,c){var u={"x-algolia-api-key":c,"x-algolia-application-id":a};return{headers:function(){return i===st.WithinHeaders?u:{}},queryParameters:function(){return i===st.WithinQueryParameters?u:{}}}}(t.authMode!==void 0?t.authMode:st.WithinHeaders,e,t.apiKey),n=function(i){var a=i.hostsCache,c=i.logger,u=i.requester,s=i.requestsCache,l=i.responsesCache,m=i.timeouts,p=i.userAgent,v=i.hosts,d=i.queryParameters,h={hostsCache:a,logger:c,requester:u,requestsCache:s,responsesCache:l,timeouts:m,userAgent:p,headers:i.headers,queryParameters:d,hosts:v.map(function(y){return Gn(y)}),read:function(y,b){var _=cn(b,h.timeouts.read),S=function(){return ln(h,h.hosts.filter(function(g){return(g.accept&me.Read)!=0}),y,_)};if((_.cacheable!==void 0?_.cacheable:y.cacheable)!==!0)return S();var O={request:y,mappedRequestOptions:_,transporter:{queryParameters:h.queryParameters,headers:h.headers}};return h.responsesCache.get(O,function(){return h.requestsCache.get(O,function(){return h.requestsCache.set(O,S()).then(function(g){return Promise.all([h.requestsCache.delete(O),g])},function(g){return Promise.all([h.requestsCache.delete(O),Promise.reject(g)])}).then(function(g){var P=se(g,2);return P[0],P[1]})})},{miss:function(g){return h.responsesCache.set(O,g)}})},write:function(y,b){return ln(h,h.hosts.filter(function(_){return(_.accept&me.Write)!=0}),y,cn(b,h.timeouts.write))}};return h}(I(I({hosts:[{url:"".concat(e,"-dsn.algolia.net"),accept:me.Read},{url:"".concat(e,".algolia.net"),accept:me.Write}].concat(ba([{url:"".concat(e,"-1.algolianet.com")},{url:"".concat(e,"-2.algolianet.com")},{url:"".concat(e,"-3.algolianet.com")}]))},t),{},{headers:I(I(I({},r.headers()),{"content-type":"application/x-www-form-urlencoded"}),t.headers),queryParameters:I(I({},r.queryParameters()),t.queryParameters)})),o={transporter:n,appId:e,addAlgoliaAgent:function(i,a){n.userAgent.add({segment:i,version:a})},clearCache:function(){return Promise.all([n.requestsCache.clear(),n.responsesCache.clear()]).then(function(){})}};return $n(o,t.methods)},Ea=function(t){return function(e,r){return e.method===$t?t.transporter.read(e,r):t.transporter.write(e,r)}},to=function(t){return function(e){var r=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},n={transporter:t.transporter,appId:t.appId,indexName:e};return $n(n,r.methods)}},fn=function(t){return function(e,r){var n=e.map(function(o){return I(I({},o),{},{params:Xn(o.params||{})})});return t.transporter.read({method:_t,path:"1/indexes/*/queries",data:{requests:n},cacheable:!0},r)}},mn=function(t){return function(e,r){return Promise.all(e.map(function(n){var o=n.params,i=o.facetName,a=o.facetQuery,c=Oo(o,ya);return to(t)(n.indexName,{methods:{searchForFacetValues:ro}}).searchForFacetValues(i,a,I(I({},r),c))}))}},Pa=function(t){return function(e,r,n){return t.transporter.read({method:_t,path:bt("1/answers/%s/prediction",t.indexName),data:{query:e,queryLanguages:r},cacheable:!0},n)}},Ia=function(t){return function(e,r){return t.transporter.read({method:_t,path:bt("1/indexes/%s/query",t.indexName),data:{query:e},cacheable:!0},r)}},ro=function(t){return function(e,r,n){return t.transporter.read({method:_t,path:bt("1/indexes/%s/facets/%s/query",t.indexName,e),data:{facetQuery:r},cacheable:!0},n)}},Da=1,ka=2,Aa=3;function no(t,e,r){var n,o={appId:t,apiKey:e,timeouts:{connect:1,read:2,write:30},requester:{send:function(i){return new Promise(function(a){var c=new XMLHttpRequest;c.open(i.method,i.url,!0),Object.keys(i.headers).forEach(function(m){return c.setRequestHeader(m,i.headers[m])});var u,s=function(m,p){return setTimeout(function(){c.abort(),a({status:0,content:p,isTimedOut:!0})},1e3*m)},l=s(i.connectTimeout,"Connection timeout");c.onreadystatechange=function(){c.readyState>c.OPENED&&u===void 0&&(clearTimeout(l),u=s(i.responseTimeout,"Socket timeout"))},c.onerror=function(){c.status===0&&(clearTimeout(l),clearTimeout(u),a({content:c.responseText||"Network request failed",status:c.status,isTimedOut:!1}))},c.onload=function(){clearTimeout(l),clearTimeout(u),a({content:c.responseText,status:c.status,isTimedOut:!1})},c.send(i.data)})}},logger:(n=Aa,{debug:function(i,a){return Da>=n&&console.debug(i,a),Promise.resolve()},info:function(i,a){return ka>=n&&console.info(i,a),Promise.resolve()},error:function(i,a){return console.error(i,a),Promise.resolve()}}),responsesCache:Tt(),requestsCache:Tt({serializable:!1}),hostsCache:Ee({caches:[ga({key:"".concat("4.19.1","-").concat(t)}),Tt()]}),userAgent:Sa("4.19.1").add({segment:"Browser",version:"lite"}),authMode:st.WithinQueryParameters};return ja(I(I(I({},o),r),{},{methods:{search:fn,searchForFacetValues:mn,multipleQueries:fn,multipleSearchForFacetValues:mn,customRequest:Ea,initIndex:function(i){return function(a){return to(i)(a,{methods:{search:Ia,searchForFacetValues:ro,findAnswers:Pa}})}}}}))}no.version="4.19.1";var Ca=["footer","searchBox"];function Be(){return Be=Object.assign||function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=new Array(e);r=0||(l[u]=a[u]);return l}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function Ra(t){var e=t.appId,r=t.apiKey,n=t.indexName,o=t.placeholder,i=o===void 0?"Search docs":o,a=t.searchParameters,c=t.maxResultsPerGroup,u=t.onClose,s=u===void 0?ra:u,l=t.transformItems,m=l===void 0?an:l,p=t.hitComponent,v=p===void 0?Li:p,d=t.resultsFooterComponent,h=d===void 0?function(){return null}:d,y=t.navigator,b=t.initialScrollY,_=b===void 0?0:b,S=t.transformSearchClient,O=S===void 0?an:S,g=t.disableUserPersonalization,P=g!==void 0&&g,C=t.initialQuery,L=C===void 0?"":C,x=t.translations,k=x===void 0?{}:x,N=t.getMissingResultsUrl,U=t.insights,F=U!==void 0&&U,M=k.footer,Ot=k.searchBox,St=Ta(k,Ca),$e=Na(f.useState({query:"",collections:[],completion:null,context:{},isOpen:!1,activeItemId:null,status:"idle"}),2),B=$e[0],oo=$e[1],Xt=f.useRef(null),wt=f.useRef(null),er=f.useRef(null),Qe=f.useRef(null),he=f.useRef(null),Q=f.useRef(10),tr=f.useRef(typeof window<"u"?window.getSelection().toString().slice(0,64):"").current,ee=f.useRef(L||tr).current,rr=function(j,D,T){return f.useMemo(function(){var H=no(j,D);return H.addAlgoliaAgent("docsearch","3.5.2"),/docsearch.js \(.*\)/.test(H.transporter.userAgent.value)===!1&&H.addAlgoliaAgent("docsearch-react","3.5.2"),T(H)},[j,D,T])}(e,r,O),oe=f.useRef(un({key:"__DOCSEARCH_FAVORITE_SEARCHES__".concat(n),limit:10})).current,ye=f.useRef(un({key:"__DOCSEARCH_RECENT_SEARCHES__".concat(n),limit:oe.getAll().length===0?7:4})).current,ge=f.useCallback(function(j){if(!P){var D=j.type==="content"?j.__docsearch_parent:j;D&&oe.getAll().findIndex(function(T){return T.objectID===D.objectID})===-1&&ye.add(D)}},[oe,ye,P]),io=f.useCallback(function(j){if(B.context.algoliaInsightsPlugin&&j.__autocomplete_id){var D=j,T={eventName:"Item Selected",index:D.__autocomplete_indexName,items:[D],positions:[j.__autocomplete_id],queryID:D.__autocomplete_queryID};B.context.algoliaInsightsPlugin.insights.clickedObjectIDsAfterSearch(T)}},[B.context.algoliaInsightsPlugin]),be=f.useMemo(function(){return Ti({id:"docsearch",defaultActiveItemId:0,placeholder:i,openOnFocus:!0,initialState:{query:ee,context:{searchSuggestions:[]}},insights:F,navigator:y,onStateChange:function(j){oo(j.state)},getSources:function(j){var D=j.query,T=j.state,H=j.setContext,Z=j.setStatus;if(!D)return P?[]:[{sourceId:"recentSearches",onSelect:function(A){var V=A.item,_e=A.event;ge(V),at(_e)||s()},getItemUrl:function(A){return A.item.url},getItems:function(){return ye.getAll()}},{sourceId:"favoriteSearches",onSelect:function(A){var V=A.item,_e=A.event;ge(V),at(_e)||s()},getItemUrl:function(A){return A.item.url},getItems:function(){return oe.getAll()}}];var Y=!!F;return rr.search([{query:D,indexName:n,params:Rt({attributesToRetrieve:["hierarchy.lvl0","hierarchy.lvl1","hierarchy.lvl2","hierarchy.lvl3","hierarchy.lvl4","hierarchy.lvl5","hierarchy.lvl6","content","type","url"],attributesToSnippet:["hierarchy.lvl1:".concat(Q.current),"hierarchy.lvl2:".concat(Q.current),"hierarchy.lvl3:".concat(Q.current),"hierarchy.lvl4:".concat(Q.current),"hierarchy.lvl5:".concat(Q.current),"hierarchy.lvl6:".concat(Q.current),"content:".concat(Q.current)],snippetEllipsisText:"…",highlightPreTag:"",highlightPostTag:"",hitsPerPage:20,clickAnalytics:Y},a)}]).catch(function(A){throw A.name==="RetryError"&&Z("error"),A}).then(function(A){var V=A.results[0],_e=V.hits,co=V.nbHits,jt=on(_e,function(Et){return Jn(Et)},c);T.context.searchSuggestions.length0&&(nr(),he.current&&he.current.focus())},[ee,nr]),f.useEffect(function(){function j(){if(wt.current){var D=.01*window.innerHeight;wt.current.style.setProperty("--docsearch-vh","".concat(D,"px"))}}return j(),window.addEventListener("resize",j),function(){window.removeEventListener("resize",j)}},[]),f.createElement("div",Be({ref:Xt},uo({"aria-expanded":!0}),{className:["DocSearch","DocSearch-Container",B.status==="stalled"&&"DocSearch-Container--Stalled",B.status==="error"&&"DocSearch-Container--Errored"].filter(Boolean).join(" "),role:"button",tabIndex:0,onMouseDown:function(j){j.target===j.currentTarget&&s()}}),f.createElement("div",{className:"DocSearch-Modal",ref:wt},f.createElement("header",{className:"DocSearch-SearchBar",ref:er},f.createElement(pa,Be({},be,{state:B,autoFocus:ee.length===0,inputRef:he,isFromSelection:!!ee&&ee===tr,translations:Ot,onClose:s}))),f.createElement("div",{className:"DocSearch-Dropdown",ref:Qe},f.createElement(sa,Be({},be,{indexName:n,state:B,hitComponent:v,resultsFooterComponent:h,disableUserPersonalization:P,recentSearches:ye,favoriteSearches:oe,inputRef:he,translations:St,getMissingResultsUrl:N,onItemClick:function(j,D){io(j),ge(j),at(D)||s()}}))),f.createElement("footer",{className:"DocSearch-Footer"},f.createElement(qi,{translations:M}))))}function Qt(){return Qt=Object.assign||function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=new Array(e);r1&&arguments[1]!==void 0?arguments[1]:window;return typeof e=="string"?r.document.querySelector(e):e}(t.container,t.environment))}const Ma={id:"docsearch"},Fa=so({__name:"VPAlgoliaSearchBox",props:{algolia:{}},setup(t){const e=t,r=fo(),n=mo(),{site:o,localeIndex:i,lang:a}=bo();po(c),vo(i,c);async function c(){var v,d;await ho();const l={...e.algolia,...(v=e.algolia.locales)==null?void 0:v[i.value]},m=((d=l.searchParameters)==null?void 0:d.facetFilters)??[],p=[...(Array.isArray(m)?m:[m]).filter(h=>!h.startsWith("lang:")),`lang:${a.value}`];u({...l,searchParameters:{...l.searchParameters,facetFilters:p}})}function u(l){const m=Object.assign({},l,{container:"#docsearch",navigator:{navigate({itemUrl:p}){const{pathname:v}=new URL(window.location.origin+p);n.path===v?window.location.assign(window.location.origin+p):r.go(p)}},transformItems(p){return p.map(v=>Object.assign({},v,{url:s(v.url)}))},hitComponent({hit:p,children:v}){return{__v:null,type:"a",ref:void 0,constructor:void 0,key:void 0,props:{href:p.url,children:v}}}});La(m)}function s(l){const{pathname:m,hash:p}=new URL(l,location.origin);return m.replace(/\.html$/,o.value.cleanUrls?"":".html")+p}return(l,m)=>(yo(),go("div",Ma))}});export{Fa as default}; diff --git a/assets/chunks/framework.g9eZ-ZSs.js b/assets/chunks/framework.g9eZ-ZSs.js new file mode 100644 index 000000000..73ac3bd2d --- /dev/null +++ b/assets/chunks/framework.g9eZ-ZSs.js @@ -0,0 +1,2 @@ +function di(e,t){const n=Object.create(null),i=e.split(",");for(let s=0;s!!n[s.toLowerCase()]:s=>!!n[s]}const te={},ht=[],Ie=()=>{},sr=()=>!1,qt=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),mi=e=>e.startsWith("onUpdate:"),re=Object.assign,hi=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},or=Object.prototype.hasOwnProperty,X=(e,t)=>or.call(e,t),D=Array.isArray,gt=e=>Cn(e)==="[object Map]",Ls=e=>Cn(e)==="[object Set]",W=e=>typeof e=="function",se=e=>typeof e=="string",Tt=e=>typeof e=="symbol",ee=e=>e!==null&&typeof e=="object",Ns=e=>(ee(e)||W(e))&&W(e.then)&&W(e.catch),ks=Object.prototype.toString,Cn=e=>ks.call(e),rr=e=>Cn(e).slice(8,-1),Hs=e=>Cn(e)==="[object Object]",gi=e=>se(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Ft=di(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),En=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},lr=/-(\w)/g,Fe=En(e=>e.replace(lr,(t,n)=>n?n.toUpperCase():"")),ar=/\B([A-Z])/g,ut=En(e=>e.replace(ar,"-$1").toLowerCase()),Tn=En(e=>e.charAt(0).toUpperCase()+e.slice(1)),fn=En(e=>e?`on${Tn(e)}`:""),ct=(e,t)=>!Object.is(e,t),Bn=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},cr=e=>{const t=parseFloat(e);return isNaN(t)?e:t},fr=e=>{const t=se(e)?Number(e):NaN;return isNaN(t)?e:t};let Bi;const Gn=()=>Bi||(Bi=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function xi(e){if(D(e)){const t={};for(let n=0;n{if(n){const i=n.split(pr);i.length>1&&(t[i[0].trim()]=i[1].trim())}}),t}function vi(e){let t="";if(se(e))t=e;else if(D(e))for(let n=0;nse(e)?e:e==null?"":D(e)||ee(e)&&(e.toString===ks||!W(e.toString))?JSON.stringify(e,Ds,2):String(e),Ds=(e,t)=>t&&t.__v_isRef?Ds(e,t.value):gt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[i,s],o)=>(n[Un(i,o)+" =>"]=s,n),{})}:Ls(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>Un(n))}:Tt(t)?Un(t):ee(t)&&!D(t)&&!Hs(t)?String(t):t,Un=(e,t="")=>{var n;return Tt(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};let ye;class xr{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=ye,!t&&ye&&(this.index=(ye.scopes||(ye.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=ye;try{return ye=this,t()}finally{ye=n}}}on(){ye=this}off(){ye=this.parent}stop(t){if(this._active){let n,i;for(n=0,i=this.effects.length;n{const t=new Set(e);return t.w=0,t.n=0,t},Ks=e=>(e.w&Xe)>0,Ws=e=>(e.n&Xe)>0,yr=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let i=0;i{(p==="length"||!Tt(p)&&p>=a)&&l.push(f)})}else switch(n!==void 0&&l.push(r.get(n)),t){case"add":D(e)?gi(n)&&l.push(r.get("length")):(l.push(r.get(rt)),gt(e)&&l.push(r.get(ti)));break;case"delete":D(e)||(l.push(r.get(rt)),gt(e)&&l.push(r.get(ti)));break;case"set":gt(e)&&l.push(r.get(rt));break}if(l.length===1)l[0]&&ni(l[0]);else{const a=[];for(const f of l)f&&a.push(...f);ni(yi(a))}}function ni(e,t){const n=D(e)?e:[...e];for(const i of n)i.computed&&Ki(i);for(const i of n)i.computed||Ki(i)}function Ki(e,t){(e!==Se||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}function _r(e,t){var n;return(n=mn.get(e))==null?void 0:n.get(t)}const wr=di("__proto__,__v_isRef,__isVue"),zs=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Tt)),Wi=Cr();function Cr(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const i=Q(this);for(let o=0,r=this.length;o{e[t]=function(...n){At();const i=Q(this)[t].apply(this,n);return St(),i}}),e}function Er(e){const t=Q(this);return xe(t,"has",e),t.hasOwnProperty(e)}class Ys{constructor(t=!1,n=!1){this._isReadonly=t,this._shallow=n}get(t,n,i){const s=this._isReadonly,o=this._shallow;if(n==="__v_isReactive")return!s;if(n==="__v_isReadonly")return s;if(n==="__v_isShallow")return o;if(n==="__v_raw")return i===(s?o?kr:Zs:o?Qs:Xs).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(i)?t:void 0;const r=D(t);if(!s){if(r&&X(Wi,n))return Reflect.get(Wi,n,i);if(n==="hasOwnProperty")return Er}const l=Reflect.get(t,n,i);return(Tt(n)?zs.has(n):wr(n))||(s||xe(t,"get",n),o)?l:ce(l)?r&&gi(n)?l:l.value:ee(l)?s?jn(l):Sn(l):l}}class Js extends Ys{constructor(t=!1){super(!1,t)}set(t,n,i,s){let o=t[n];if(_t(o)&&ce(o)&&!ce(i))return!1;if(!this._shallow&&(!hn(i)&&!_t(i)&&(o=Q(o),i=Q(i)),!D(t)&&ce(o)&&!ce(i)))return o.value=i,!0;const r=D(t)&&gi(n)?Number(n)e,An=e=>Reflect.getPrototypeOf(e);function Xt(e,t,n=!1,i=!1){e=e.__v_raw;const s=Q(e),o=Q(t);n||(ct(t,o)&&xe(s,"get",t),xe(s,"get",o));const{has:r}=An(s),l=i?bi:n?Ci:Dt;if(r.call(s,t))return l(e.get(t));if(r.call(s,o))return l(e.get(o));e!==s&&e.get(t)}function Qt(e,t=!1){const n=this.__v_raw,i=Q(n),s=Q(e);return t||(ct(e,s)&&xe(i,"has",e),xe(i,"has",s)),e===s?n.has(e):n.has(e)||n.has(s)}function Zt(e,t=!1){return e=e.__v_raw,!t&&xe(Q(e),"iterate",rt),Reflect.get(e,"size",e)}function qi(e){e=Q(e);const t=Q(this);return An(t).has.call(t,e)||(t.add(e),He(t,"add",e,e)),this}function Vi(e,t){t=Q(t);const n=Q(this),{has:i,get:s}=An(n);let o=i.call(n,e);o||(e=Q(e),o=i.call(n,e));const r=s.call(n,e);return n.set(e,t),o?ct(t,r)&&He(n,"set",e,t):He(n,"add",e,t),this}function zi(e){const t=Q(this),{has:n,get:i}=An(t);let s=n.call(t,e);s||(e=Q(e),s=n.call(t,e)),i&&i.call(t,e);const o=t.delete(e);return s&&He(t,"delete",e,void 0),o}function Yi(){const e=Q(this),t=e.size!==0,n=e.clear();return t&&He(e,"clear",void 0,void 0),n}function Gt(e,t){return function(i,s){const o=this,r=o.__v_raw,l=Q(r),a=t?bi:e?Ci:Dt;return!e&&xe(l,"iterate",rt),r.forEach((f,p)=>i.call(s,a(f),a(p),o))}}function en(e,t,n){return function(...i){const s=this.__v_raw,o=Q(s),r=gt(o),l=e==="entries"||e===Symbol.iterator&&r,a=e==="keys"&&r,f=s[e](...i),p=n?bi:t?Ci:Dt;return!t&&xe(o,"iterate",a?ti:rt),{next(){const{value:d,done:x}=f.next();return x?{value:d,done:x}:{value:l?[p(d[0]),p(d[1])]:p(d),done:x}},[Symbol.iterator](){return this}}}}function De(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function Or(){const e={get(o){return Xt(this,o)},get size(){return Zt(this)},has:Qt,add:qi,set:Vi,delete:zi,clear:Yi,forEach:Gt(!1,!1)},t={get(o){return Xt(this,o,!1,!0)},get size(){return Zt(this)},has:Qt,add:qi,set:Vi,delete:zi,clear:Yi,forEach:Gt(!1,!0)},n={get(o){return Xt(this,o,!0)},get size(){return Zt(this,!0)},has(o){return Qt.call(this,o,!0)},add:De("add"),set:De("set"),delete:De("delete"),clear:De("clear"),forEach:Gt(!0,!1)},i={get(o){return Xt(this,o,!0,!0)},get size(){return Zt(this,!0)},has(o){return Qt.call(this,o,!0)},add:De("add"),set:De("set"),delete:De("delete"),clear:De("clear"),forEach:Gt(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(o=>{e[o]=en(o,!1,!1),n[o]=en(o,!0,!1),t[o]=en(o,!1,!0),i[o]=en(o,!0,!0)}),[e,n,t,i]}const[Rr,Pr,Mr,Ir]=Or();function _i(e,t){const n=t?e?Ir:Mr:e?Pr:Rr;return(i,s,o)=>s==="__v_isReactive"?!e:s==="__v_isReadonly"?e:s==="__v_raw"?i:Reflect.get(X(n,s)&&s in i?n:i,s,o)}const Fr={get:_i(!1,!1)},Lr={get:_i(!1,!0)},Nr={get:_i(!0,!1)},Xs=new WeakMap,Qs=new WeakMap,Zs=new WeakMap,kr=new WeakMap;function Hr(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function $r(e){return e.__v_skip||!Object.isExtensible(e)?0:Hr(rr(e))}function Sn(e){return _t(e)?e:wi(e,!1,Ar,Fr,Xs)}function Dr(e){return wi(e,!1,jr,Lr,Qs)}function jn(e){return wi(e,!0,Sr,Nr,Zs)}function wi(e,t,n,i,s){if(!ee(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const o=s.get(e);if(o)return o;const r=$r(e);if(r===0)return e;const l=new Proxy(e,r===2?i:n);return s.set(e,l),l}function xt(e){return _t(e)?xt(e.__v_raw):!!(e&&e.__v_isReactive)}function _t(e){return!!(e&&e.__v_isReadonly)}function hn(e){return!!(e&&e.__v_isShallow)}function Gs(e){return xt(e)||_t(e)}function Q(e){const t=e&&e.__v_raw;return t?Q(t):e}function Lt(e){return dn(e,"__v_skip",!0),e}const Dt=e=>ee(e)?Sn(e):e,Ci=e=>ee(e)?jn(e):e;function Ei(e){Ve&&Se&&(e=Q(e),Vs(e.dep||(e.dep=yi())))}function Ti(e,t){e=Q(e);const n=e.dep;n&&ni(n)}function ce(e){return!!(e&&e.__v_isRef===!0)}function ue(e){return to(e,!1)}function eo(e){return to(e,!0)}function to(e,t){return ce(e)?e:new Br(e,t)}class Br{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:Q(t),this._value=n?t:Dt(t)}get value(){return Ei(this),this._value}set value(t){const n=this.__v_isShallow||hn(t)||_t(t);t=n?t:Q(t),ct(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:Dt(t),Ti(this))}}function Ai(e){return ce(e)?e.value:e}const Ur={get:(e,t,n)=>Ai(Reflect.get(e,t,n)),set:(e,t,n,i)=>{const s=e[t];return ce(s)&&!ce(n)?(s.value=n,!0):Reflect.set(e,t,n,i)}};function no(e){return xt(e)?e:new Proxy(e,Ur)}class Kr{constructor(t){this.dep=void 0,this.__v_isRef=!0;const{get:n,set:i}=t(()=>Ei(this),()=>Ti(this));this._get=n,this._set=i}get value(){return this._get()}set value(t){this._set(t)}}function Wr(e){return new Kr(e)}class qr{constructor(t,n,i){this._object=t,this._key=n,this._defaultValue=i,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return _r(Q(this._object),this._key)}}class Vr{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function zr(e,t,n){return ce(e)?e:W(e)?new Vr(e):ee(e)&&arguments.length>1?Yr(e,t,n):ue(e)}function Yr(e,t,n){const i=e[t];return ce(i)?i:new qr(e,t,n)}class Jr{constructor(t,n,i,s){this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this._dirty=!0,this.effect=new $t(t,()=>{this._dirty||(this._dirty=!0,Ti(this))}),this.effect.computed=this,this.effect.active=this._cacheable=!s,this.__v_isReadonly=i}get value(){const t=Q(this);return Ei(t),(t._dirty||!t._cacheable)&&(t._dirty=!1,t._value=t.effect.run()),t._value}set value(t){this._setter(t)}}function Xr(e,t,n=!1){let i,s;const o=W(e);return o?(i=e,s=Ie):(i=e.get,s=e.set),new Jr(i,s,o||!s,n)}function ze(e,t,n,i){let s;try{s=i?e(...i):e()}catch(o){Vt(o,t,n)}return s}function Ee(e,t,n,i){if(W(e)){const o=ze(e,t,n,i);return o&&Ns(o)&&o.catch(r=>{Vt(r,t,n)}),o}const s=[];for(let o=0;o>>1,s=de[i],o=Ut(s);oMe&&de.splice(t,1)}function el(e){D(e)?vt.push(...e):(!ke||!ke.includes(e,e.allowRecurse?nt+1:nt))&&vt.push(e),so()}function Ji(e,t,n=Bt?Me+1:0){for(;nUt(n)-Ut(i)),nt=0;nte.id==null?1/0:e.id,tl=(e,t)=>{const n=Ut(e)-Ut(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function oo(e){ii=!1,Bt=!0,de.sort(tl);try{for(Me=0;Mese(_)?_.trim():_)),d&&(s=n.map(cr))}let l,a=i[l=fn(t)]||i[l=fn(Fe(t))];!a&&o&&(a=i[l=fn(ut(t))]),a&&Ee(a,e,6,s);const f=i[l+"Once"];if(f){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,Ee(f,e,6,s)}}function ro(e,t,n=!1){const i=t.emitsCache,s=i.get(e);if(s!==void 0)return s;const o=e.emits;let r={},l=!1;if(!W(e)){const a=f=>{const p=ro(f,t,!0);p&&(l=!0,re(r,p))};!n&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}return!o&&!l?(ee(e)&&i.set(e,null),null):(D(o)?o.forEach(a=>r[a]=null):re(r,o),ee(e)&&i.set(e,r),r)}function Pn(e,t){return!e||!qt(t)?!1:(t=t.slice(2).replace(/Once$/,""),X(e,t[0].toLowerCase()+t.slice(1))||X(e,ut(t))||X(e,t))}let pe=null,Mn=null;function xn(e){const t=pe;return pe=e,Mn=e&&e.type.__scopeId||null,t}function Ac(e){Mn=e}function Sc(){Mn=null}function il(e,t=pe,n){if(!t||e._n)return e;const i=(...s)=>{i._d&&ls(-1);const o=xn(t);let r;try{r=e(...s)}finally{xn(o),i._d&&ls(1)}return r};return i._n=!0,i._c=!0,i._d=!0,i}function Kn(e){const{type:t,vnode:n,proxy:i,withProxy:s,props:o,propsOptions:[r],slots:l,attrs:a,emit:f,render:p,renderCache:d,data:x,setupState:_,ctx:E,inheritAttrs:j}=e;let F,U;const L=xn(e);try{if(n.shapeFlag&4){const m=s||i,M=m;F=Ae(p.call(M,m,d,o,_,x,E)),U=a}else{const m=t;F=Ae(m.length>1?m(o,{attrs:a,slots:l,emit:f}):m(o,null)),U=t.props?a:sl(a)}}catch(m){Ht.length=0,Vt(m,e,1),F=ie(be)}let g=F;if(U&&j!==!1){const m=Object.keys(U),{shapeFlag:M}=g;m.length&&M&7&&(r&&m.some(mi)&&(U=ol(U,r)),g=Qe(g,U))}return n.dirs&&(g=Qe(g),g.dirs=g.dirs?g.dirs.concat(n.dirs):n.dirs),n.transition&&(g.transition=n.transition),F=g,xn(L),F}const sl=e=>{let t;for(const n in e)(n==="class"||n==="style"||qt(n))&&((t||(t={}))[n]=e[n]);return t},ol=(e,t)=>{const n={};for(const i in e)(!mi(i)||!(i.slice(9)in t))&&(n[i]=e[i]);return n};function rl(e,t,n){const{props:i,children:s,component:o}=e,{props:r,children:l,patchFlag:a}=t,f=o.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&a>=0){if(a&1024)return!0;if(a&16)return i?Xi(i,r,f):!!r;if(a&8){const p=t.dynamicProps;for(let d=0;de.__isSuspense;function co(e,t){t&&t.pendingBranch?D(e)?t.effects.push(...e):t.effects.push(e):el(e)}function fo(e,t){return In(e,null,t)}function Rc(e,t){return In(e,null,{flush:"post"})}const tn={};function Ye(e,t,n){return In(e,t,n)}function In(e,t,{immediate:n,deep:i,flush:s,onTrack:o,onTrigger:r}=te){var l;const a=Us()===((l=ae)==null?void 0:l.scope)?ae:null;let f,p=!1,d=!1;if(ce(e)?(f=()=>e.value,p=hn(e)):xt(e)?(f=()=>e,i=!0):D(e)?(d=!0,p=e.some(m=>xt(m)||hn(m)),f=()=>e.map(m=>{if(ce(m))return m.value;if(xt(m))return ot(m);if(W(m))return ze(m,a,2)})):W(e)?t?f=()=>ze(e,a,2):f=()=>{if(!(a&&a.isUnmounted))return x&&x(),Ee(e,a,3,[_])}:f=Ie,t&&i){const m=f;f=()=>ot(m())}let x,_=m=>{x=L.onStop=()=>{ze(m,a,4),x=L.onStop=void 0}},E;if(Et)if(_=Ie,t?n&&Ee(t,a,3,[f(),d?[]:void 0,_]):f(),s==="sync"){const m=na();E=m.__watcherHandles||(m.__watcherHandles=[])}else return Ie;let j=d?new Array(e.length).fill(tn):tn;const F=()=>{if(L.active)if(t){const m=L.run();(i||p||(d?m.some((M,q)=>ct(M,j[q])):ct(m,j)))&&(x&&x(),Ee(t,a,3,[m,j===tn?void 0:d&&j[0]===tn?[]:j,_]),j=m)}else L.run()};F.allowRecurse=!!t;let U;s==="sync"?U=F:s==="post"?U=()=>he(F,a&&a.suspense):(F.pre=!0,a&&(F.id=a.uid),U=()=>Rn(F));const L=new $t(f,U);t?n?F():j=L.run():s==="post"?he(L.run.bind(L),a&&a.suspense):L.run();const g=()=>{L.stop(),a&&a.scope&&hi(a.scope.effects,L)};return E&&E.push(g),g}function cl(e,t,n){const i=this.proxy,s=se(e)?e.includes(".")?uo(i,e):()=>i[e]:e.bind(i,i);let o;W(t)?o=t:(o=t.handler,n=t);const r=ae;Ct(this);const l=In(s,o.bind(i),n);return r?Ct(r):lt(),l}function uo(e,t){const n=t.split(".");return()=>{let i=e;for(let s=0;s{ot(n,t)});else if(Hs(e))for(const n in e)ot(e[n],t);return e}function Pc(e,t){const n=pe;if(n===null)return e;const i=Hn(n)||n.proxy,s=e.dirs||(e.dirs=[]);for(let o=0;o{e.isMounted=!0}),xo(()=>{e.isUnmounting=!0}),e}const _e=[Function,Array],po={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:_e,onEnter:_e,onAfterEnter:_e,onEnterCancelled:_e,onBeforeLeave:_e,onLeave:_e,onAfterLeave:_e,onLeaveCancelled:_e,onBeforeAppear:_e,onAppear:_e,onAfterAppear:_e,onAppearCancelled:_e},ul={name:"BaseTransition",props:po,setup(e,{slots:t}){const n=kn(),i=fl();let s;return()=>{const o=t.default&&ho(t.default(),!0);if(!o||!o.length)return;let r=o[0];if(o.length>1){for(const j of o)if(j.type!==be){r=j;break}}const l=Q(e),{mode:a}=l;if(i.isLeaving)return Wn(r);const f=Zi(r);if(!f)return Wn(r);const p=si(f,l,i,n);oi(f,p);const d=n.subTree,x=d&&Zi(d);let _=!1;const{getTransitionKey:E}=f.type;if(E){const j=E();s===void 0?s=j:j!==s&&(s=j,_=!0)}if(x&&x.type!==be&&(!it(f,x)||_)){const j=si(x,l,i,n);if(oi(x,j),a==="out-in")return i.isLeaving=!0,j.afterLeave=()=>{i.isLeaving=!1,n.update.active!==!1&&n.update()},Wn(r);a==="in-out"&&f.type!==be&&(j.delayLeave=(F,U,L)=>{const g=mo(i,x);g[String(x.key)]=x,F[We]=()=>{U(),F[We]=void 0,delete p.delayedLeave},p.delayedLeave=L})}return r}}},pl=ul;function mo(e,t){const{leavingVNodes:n}=e;let i=n.get(t.type);return i||(i=Object.create(null),n.set(t.type,i)),i}function si(e,t,n,i){const{appear:s,mode:o,persisted:r=!1,onBeforeEnter:l,onEnter:a,onAfterEnter:f,onEnterCancelled:p,onBeforeLeave:d,onLeave:x,onAfterLeave:_,onLeaveCancelled:E,onBeforeAppear:j,onAppear:F,onAfterAppear:U,onAppearCancelled:L}=t,g=String(e.key),m=mo(n,e),M=(P,A)=>{P&&Ee(P,i,9,A)},q=(P,A)=>{const S=A[1];M(P,A),D(P)?P.every(V=>V.length<=1)&&S():P.length<=1&&S()},B={mode:o,persisted:r,beforeEnter(P){let A=l;if(!n.isMounted)if(s)A=j||l;else return;P[We]&&P[We](!0);const S=m[g];S&&it(e,S)&&S.el[We]&&S.el[We](),M(A,[P])},enter(P){let A=a,S=f,V=p;if(!n.isMounted)if(s)A=F||a,S=U||f,V=L||p;else return;let O=!1;const z=P[nn]=le=>{O||(O=!0,le?M(V,[P]):M(S,[P]),B.delayedLeave&&B.delayedLeave(),P[nn]=void 0)};A?q(A,[P,z]):z()},leave(P,A){const S=String(e.key);if(P[nn]&&P[nn](!0),n.isUnmounting)return A();M(d,[P]);let V=!1;const O=P[We]=z=>{V||(V=!0,A(),z?M(E,[P]):M(_,[P]),P[We]=void 0,m[S]===e&&delete m[S])};m[S]=e,x?q(x,[P,O]):O()},clone(P){return si(P,t,n,i)}};return B}function Wn(e){if(zt(e))return e=Qe(e),e.children=null,e}function Zi(e){return zt(e)?e.children?e.children[0]:void 0:e}function oi(e,t){e.shapeFlag&6&&e.component?oi(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function ho(e,t=!1,n){let i=[],s=0;for(let o=0;o1)for(let o=0;o!!e.type.__asyncLoader;/*! #__NO_SIDE_EFFECTS__ */function Mc(e){W(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:i,delay:s=200,timeout:o,suspensible:r=!0,onError:l}=e;let a=null,f,p=0;const d=()=>(p++,a=null,x()),x=()=>{let _;return a||(_=a=t().catch(E=>{if(E=E instanceof Error?E:new Error(String(E)),l)return new Promise((j,F)=>{l(E,()=>j(d()),()=>F(E),p+1)});throw E}).then(E=>_!==a&&a?a:(E&&(E.__esModule||E[Symbol.toStringTag]==="Module")&&(E=E.default),f=E,E)))};return Oi({name:"AsyncComponentWrapper",__asyncLoader:x,get __asyncResolved(){return f},setup(){const _=ae;if(f)return()=>qn(f,_);const E=L=>{a=null,Vt(L,_,13,!i)};if(r&&_.suspense||Et)return x().then(L=>()=>qn(L,_)).catch(L=>(E(L),()=>i?ie(i,{error:L}):null));const j=ue(!1),F=ue(),U=ue(!!s);return s&&setTimeout(()=>{U.value=!1},s),o!=null&&setTimeout(()=>{if(!j.value&&!F.value){const L=new Error(`Async component timed out after ${o}ms.`);E(L),F.value=L}},o),x().then(()=>{j.value=!0,_.parent&&zt(_.parent.vnode)&&Rn(_.parent.update)}).catch(L=>{E(L),F.value=L}),()=>{if(j.value&&f)return qn(f,_);if(F.value&&i)return ie(i,{error:F.value});if(n&&!U.value)return ie(n)}}})}function qn(e,t){const{ref:n,props:i,children:s,ce:o}=t.vnode,r=ie(e,i,s);return r.ref=n,r.ce=o,delete t.vnode.ce,r}const zt=e=>e.type.__isKeepAlive;function dl(e,t){go(e,"a",t)}function ml(e,t){go(e,"da",t)}function go(e,t,n=ae){const i=e.__wdc||(e.__wdc=()=>{let s=n;for(;s;){if(s.isDeactivated)return;s=s.parent}return e()});if(Fn(t,i,n),n){let s=n.parent;for(;s&&s.parent;)zt(s.parent.vnode)&&hl(i,t,n,s),s=s.parent}}function hl(e,t,n,i){const s=Fn(t,e,i,!0);Ln(()=>{hi(i[t],s)},n)}function Fn(e,t,n=ae,i=!1){if(n){const s=n[e]||(n[e]=[]),o=t.__weh||(t.__weh=(...r)=>{if(n.isUnmounted)return;At(),Ct(n);const l=Ee(t,n,e,r);return lt(),St(),l});return i?s.unshift(o):s.push(o),o}}const $e=e=>(t,n=ae)=>(!Et||e==="sp")&&Fn(e,(...i)=>t(...i),n),gl=$e("bm"),jt=$e("m"),xl=$e("bu"),vl=$e("u"),xo=$e("bum"),Ln=$e("um"),yl=$e("sp"),bl=$e("rtg"),_l=$e("rtc");function wl(e,t=ae){Fn("ec",e,t)}function Ic(e,t,n,i){let s;const o=n&&n[i];if(D(e)||se(e)){s=new Array(e.length);for(let r=0,l=e.length;rt(r,l,void 0,o&&o[l]));else{const r=Object.keys(e);s=new Array(r.length);for(let l=0,a=r.length;l_n(t)?!(t.type===be||t.type===ge&&!vo(t.children)):!0)?e:null}function Lc(e,t){const n={};for(const i in e)n[t&&/[A-Z]/.test(i)?`on:${i}`:fn(i)]=e[i];return n}const ri=e=>e?Fo(e)?Hn(e)||e.proxy:ri(e.parent):null,Nt=re(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>ri(e.parent),$root:e=>ri(e.root),$emit:e=>e.emit,$options:e=>Ri(e),$forceUpdate:e=>e.f||(e.f=()=>Rn(e.update)),$nextTick:e=>e.n||(e.n=On.bind(e.proxy)),$watch:e=>cl.bind(e)}),Vn=(e,t)=>e!==te&&!e.__isScriptSetup&&X(e,t),Cl={get({_:e},t){const{ctx:n,setupState:i,data:s,props:o,accessCache:r,type:l,appContext:a}=e;let f;if(t[0]!=="$"){const _=r[t];if(_!==void 0)switch(_){case 1:return i[t];case 2:return s[t];case 4:return n[t];case 3:return o[t]}else{if(Vn(i,t))return r[t]=1,i[t];if(s!==te&&X(s,t))return r[t]=2,s[t];if((f=e.propsOptions[0])&&X(f,t))return r[t]=3,o[t];if(n!==te&&X(n,t))return r[t]=4,n[t];li&&(r[t]=0)}}const p=Nt[t];let d,x;if(p)return t==="$attrs"&&xe(e,"get",t),p(e);if((d=l.__cssModules)&&(d=d[t]))return d;if(n!==te&&X(n,t))return r[t]=4,n[t];if(x=a.config.globalProperties,X(x,t))return x[t]},set({_:e},t,n){const{data:i,setupState:s,ctx:o}=e;return Vn(s,t)?(s[t]=n,!0):i!==te&&X(i,t)?(i[t]=n,!0):X(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(o[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:i,appContext:s,propsOptions:o}},r){let l;return!!n[r]||e!==te&&X(e,r)||Vn(t,r)||(l=o[0])&&X(l,r)||X(i,r)||X(Nt,r)||X(s.config.globalProperties,r)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:X(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Nc(){return El().slots}function El(){const e=kn();return e.setupContext||(e.setupContext=No(e))}function Gi(e){return D(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let li=!0;function Tl(e){const t=Ri(e),n=e.proxy,i=e.ctx;li=!1,t.beforeCreate&&es(t.beforeCreate,e,"bc");const{data:s,computed:o,methods:r,watch:l,provide:a,inject:f,created:p,beforeMount:d,mounted:x,beforeUpdate:_,updated:E,activated:j,deactivated:F,beforeDestroy:U,beforeUnmount:L,destroyed:g,unmounted:m,render:M,renderTracked:q,renderTriggered:B,errorCaptured:P,serverPrefetch:A,expose:S,inheritAttrs:V,components:O,directives:z,filters:le}=t;if(f&&Al(f,i,null),r)for(const J in r){const k=r[J];W(k)&&(i[J]=k.bind(n))}if(s){const J=s.call(n,n);ee(J)&&(e.data=Sn(J))}if(li=!0,o)for(const J in o){const k=o[J],Le=W(k)?k.bind(n,n):W(k.get)?k.get.bind(n,n):Ie,Yt=!W(k)&&W(k.set)?k.set.bind(n):Ie,Ze=ne({get:Le,set:Yt});Object.defineProperty(i,J,{enumerable:!0,configurable:!0,get:()=>Ze.value,set:Oe=>Ze.value=Oe})}if(l)for(const J in l)yo(l[J],i,n,J);if(a){const J=W(a)?a.call(n):a;Reflect.ownKeys(J).forEach(k=>{Ml(k,J[k])})}p&&es(p,e,"c");function N(J,k){D(k)?k.forEach(Le=>J(Le.bind(n))):k&&J(k.bind(n))}if(N(gl,d),N(jt,x),N(xl,_),N(vl,E),N(dl,j),N(ml,F),N(wl,P),N(_l,q),N(bl,B),N(xo,L),N(Ln,m),N(yl,A),D(S))if(S.length){const J=e.exposed||(e.exposed={});S.forEach(k=>{Object.defineProperty(J,k,{get:()=>n[k],set:Le=>n[k]=Le})})}else e.exposed||(e.exposed={});M&&e.render===Ie&&(e.render=M),V!=null&&(e.inheritAttrs=V),O&&(e.components=O),z&&(e.directives=z)}function Al(e,t,n=Ie){D(e)&&(e=ai(e));for(const i in e){const s=e[i];let o;ee(s)?"default"in s?o=bt(s.from||i,s.default,!0):o=bt(s.from||i):o=bt(s),ce(o)?Object.defineProperty(t,i,{enumerable:!0,configurable:!0,get:()=>o.value,set:r=>o.value=r}):t[i]=o}}function es(e,t,n){Ee(D(e)?e.map(i=>i.bind(t.proxy)):e.bind(t.proxy),t,n)}function yo(e,t,n,i){const s=i.includes(".")?uo(n,i):()=>n[i];if(se(e)){const o=t[e];W(o)&&Ye(s,o)}else if(W(e))Ye(s,e.bind(n));else if(ee(e))if(D(e))e.forEach(o=>yo(o,t,n,i));else{const o=W(e.handler)?e.handler.bind(n):t[e.handler];W(o)&&Ye(s,o,e)}}function Ri(e){const t=e.type,{mixins:n,extends:i}=t,{mixins:s,optionsCache:o,config:{optionMergeStrategies:r}}=e.appContext,l=o.get(t);let a;return l?a=l:!s.length&&!n&&!i?a=t:(a={},s.length&&s.forEach(f=>vn(a,f,r,!0)),vn(a,t,r)),ee(t)&&o.set(t,a),a}function vn(e,t,n,i=!1){const{mixins:s,extends:o}=t;o&&vn(e,o,n,!0),s&&s.forEach(r=>vn(e,r,n,!0));for(const r in t)if(!(i&&r==="expose")){const l=Sl[r]||n&&n[r];e[r]=l?l(e[r],t[r]):t[r]}return e}const Sl={data:ts,props:ns,emits:ns,methods:It,computed:It,beforeCreate:me,created:me,beforeMount:me,mounted:me,beforeUpdate:me,updated:me,beforeDestroy:me,beforeUnmount:me,destroyed:me,unmounted:me,activated:me,deactivated:me,errorCaptured:me,serverPrefetch:me,components:It,directives:It,watch:Ol,provide:ts,inject:jl};function ts(e,t){return t?e?function(){return re(W(e)?e.call(this,this):e,W(t)?t.call(this,this):t)}:t:e}function jl(e,t){return It(ai(e),ai(t))}function ai(e){if(D(e)){const t={};for(let n=0;n1)return n&&W(t)?t.call(i&&i.proxy):t}}function Il(e,t,n,i=!1){const s={},o={};dn(o,Nn,1),e.propsDefaults=Object.create(null),_o(e,t,s,o);for(const r in e.propsOptions[0])r in s||(s[r]=void 0);n?e.props=i?s:Dr(s):e.type.props?e.props=s:e.props=o,e.attrs=o}function Fl(e,t,n,i){const{props:s,attrs:o,vnode:{patchFlag:r}}=e,l=Q(s),[a]=e.propsOptions;let f=!1;if((i||r>0)&&!(r&16)){if(r&8){const p=e.vnode.dynamicProps;for(let d=0;d{a=!0;const[x,_]=wo(d,t,!0);re(r,x),_&&l.push(..._)};!n&&t.mixins.length&&t.mixins.forEach(p),e.extends&&p(e.extends),e.mixins&&e.mixins.forEach(p)}if(!o&&!a)return ee(e)&&i.set(e,ht),ht;if(D(o))for(let p=0;p-1,_[1]=j<0||E-1||X(_,"default"))&&l.push(d)}}}const f=[r,l];return ee(e)&&i.set(e,f),f}function is(e){return e[0]!=="$"}function ss(e){const t=e&&e.toString().match(/^\s*(function|class) (\w+)/);return t?t[2]:e===null?"null":""}function os(e,t){return ss(e)===ss(t)}function rs(e,t){return D(t)?t.findIndex(n=>os(n,e)):W(t)&&os(t,e)?0:-1}const Co=e=>e[0]==="_"||e==="$stable",Pi=e=>D(e)?e.map(Ae):[Ae(e)],Ll=(e,t,n)=>{if(t._n)return t;const i=il((...s)=>Pi(t(...s)),n);return i._c=!1,i},Eo=(e,t,n)=>{const i=e._ctx;for(const s in e){if(Co(s))continue;const o=e[s];if(W(o))t[s]=Ll(s,o,i);else if(o!=null){const r=Pi(o);t[s]=()=>r}}},To=(e,t)=>{const n=Pi(t);e.slots.default=()=>n},Nl=(e,t)=>{if(e.vnode.shapeFlag&32){const n=t._;n?(e.slots=Q(t),dn(t,"_",n)):Eo(t,e.slots={})}else e.slots={},t&&To(e,t);dn(e.slots,Nn,1)},kl=(e,t,n)=>{const{vnode:i,slots:s}=e;let o=!0,r=te;if(i.shapeFlag&32){const l=t._;l?n&&l===1?o=!1:(re(s,t),!n&&l===1&&delete s._):(o=!t.$stable,Eo(t,s)),r=t}else t&&(To(e,t),r={default:1});if(o)for(const l in s)!Co(l)&&r[l]==null&&delete s[l]};function bn(e,t,n,i,s=!1){if(D(e)){e.forEach((x,_)=>bn(x,t&&(D(t)?t[_]:t),n,i,s));return}if(yt(i)&&!s)return;const o=i.shapeFlag&4?Hn(i.component)||i.component.proxy:i.el,r=s?null:o,{i:l,r:a}=e,f=t&&t.r,p=l.refs===te?l.refs={}:l.refs,d=l.setupState;if(f!=null&&f!==a&&(se(f)?(p[f]=null,X(d,f)&&(d[f]=null)):ce(f)&&(f.value=null)),W(a))ze(a,l,12,[r,p]);else{const x=se(a),_=ce(a);if(x||_){const E=()=>{if(e.f){const j=x?X(d,a)?d[a]:p[a]:a.value;s?D(j)&&hi(j,o):D(j)?j.includes(o)||j.push(o):x?(p[a]=[o],X(d,a)&&(d[a]=p[a])):(a.value=[o],e.k&&(p[e.k]=a.value))}else x?(p[a]=r,X(d,a)&&(d[a]=r)):_&&(a.value=r,e.k&&(p[e.k]=r))};r?(E.id=-1,he(E,n)):E()}}}let Be=!1;const sn=e=>/svg/.test(e.namespaceURI)&&e.tagName!=="foreignObject",on=e=>e.nodeType===8;function Hl(e){const{mt:t,p:n,o:{patchProp:i,createText:s,nextSibling:o,parentNode:r,remove:l,insert:a,createComment:f}}=e,p=(g,m)=>{if(!m.hasChildNodes()){n(null,g,m),gn(),m._vnode=g;return}Be=!1,d(m.firstChild,g,null,null,null),gn(),m._vnode=g,Be&&console.error("Hydration completed but contains mismatches.")},d=(g,m,M,q,B,P=!1)=>{const A=on(g)&&g.data==="[",S=()=>j(g,m,M,q,B,A),{type:V,ref:O,shapeFlag:z,patchFlag:le}=m;let fe=g.nodeType;m.el=g,le===-2&&(P=!1,m.dynamicChildren=null);let N=null;switch(V){case wt:fe!==3?m.children===""?(a(m.el=s(""),r(g),g),N=g):N=S():(g.data!==m.children&&(Be=!0,g.data=m.children),N=o(g));break;case be:L(g)?(N=o(g),U(m.el=g.content.firstChild,g,M)):fe!==8||A?N=S():N=o(g);break;case kt:if(A&&(g=o(g),fe=g.nodeType),fe===1||fe===3){N=g;const J=!m.children.length;for(let k=0;k{P=P||!!m.dynamicChildren;const{type:A,props:S,patchFlag:V,shapeFlag:O,dirs:z,transition:le}=m,fe=A==="input"||A==="option";if(fe||V!==-1){if(z&&Pe(m,null,M,"created"),S)if(fe||!P||V&48)for(const k in S)(fe&&(k.endsWith("value")||k==="indeterminate")||qt(k)&&!Ft(k)||k[0]===".")&&i(g,k,null,S[k],!1,void 0,M);else S.onClick&&i(g,"onClick",null,S.onClick,!1,void 0,M);let N;(N=S&&S.onVnodeBeforeMount)&&we(N,M,m);let J=!1;if(L(g)){J=Ao(q,le)&&M&&M.vnode.props&&M.vnode.props.appear;const k=g.content.firstChild;J&&le.beforeEnter(k),U(k,g,M),m.el=g=k}if(z&&Pe(m,null,M,"beforeMount"),((N=S&&S.onVnodeMounted)||z||J)&&co(()=>{N&&we(N,M,m),J&&le.enter(g),z&&Pe(m,null,M,"mounted")},q),O&16&&!(S&&(S.innerHTML||S.textContent))){let k=_(g.firstChild,m,g,M,q,B,P);for(;k;){Be=!0;const Le=k;k=k.nextSibling,l(Le)}}else O&8&&g.textContent!==m.children&&(Be=!0,g.textContent=m.children)}return g.nextSibling},_=(g,m,M,q,B,P,A)=>{A=A||!!m.dynamicChildren;const S=m.children,V=S.length;for(let O=0;O{const{slotScopeIds:A}=m;A&&(B=B?B.concat(A):A);const S=r(g),V=_(o(g),m,S,M,q,B,P);return V&&on(V)&&V.data==="]"?o(m.anchor=V):(Be=!0,a(m.anchor=f("]"),S,V),V)},j=(g,m,M,q,B,P)=>{if(Be=!0,m.el=null,P){const V=F(g);for(;;){const O=o(g);if(O&&O!==V)l(O);else break}}const A=o(g),S=r(g);return l(g),n(null,m,S,A,M,q,sn(S),B),A},F=(g,m="[",M="]")=>{let q=0;for(;g;)if(g=o(g),g&&on(g)&&(g.data===m&&q++,g.data===M)){if(q===0)return o(g);q--}return g},U=(g,m,M)=>{const q=m.parentNode;q&&q.replaceChild(g,m);let B=M;for(;B;)B.vnode.el===m&&(B.vnode.el=B.subTree.el=g),B=B.parent},L=g=>g.nodeType===1&&g.tagName.toLowerCase()==="template";return[p,d]}const he=co;function $l(e){return Dl(e,Hl)}function Dl(e,t){const n=Gn();n.__VUE__=!0;const{insert:i,remove:s,patchProp:o,createElement:r,createText:l,createComment:a,setText:f,setElementText:p,parentNode:d,nextSibling:x,setScopeId:_=Ie,insertStaticContent:E}=e,j=(c,u,h,v=null,y=null,C=null,R=!1,w=null,T=!!u.dynamicChildren)=>{if(c===u)return;c&&!it(c,u)&&(v=Jt(c),Oe(c,y,C,!0),c=null),u.patchFlag===-2&&(T=!1,u.dynamicChildren=null);const{type:b,ref:H,shapeFlag:I}=u;switch(b){case wt:F(c,u,h,v);break;case be:U(c,u,h,v);break;case kt:c==null&&L(u,h,v,R);break;case ge:O(c,u,h,v,y,C,R,w,T);break;default:I&1?M(c,u,h,v,y,C,R,w,T):I&6?z(c,u,h,v,y,C,R,w,T):(I&64||I&128)&&b.process(c,u,h,v,y,C,R,w,T,pt)}H!=null&&y&&bn(H,c&&c.ref,C,u||c,!u)},F=(c,u,h,v)=>{if(c==null)i(u.el=l(u.children),h,v);else{const y=u.el=c.el;u.children!==c.children&&f(y,u.children)}},U=(c,u,h,v)=>{c==null?i(u.el=a(u.children||""),h,v):u.el=c.el},L=(c,u,h,v)=>{[c.el,c.anchor]=E(c.children,u,h,v,c.el,c.anchor)},g=({el:c,anchor:u},h,v)=>{let y;for(;c&&c!==u;)y=x(c),i(c,h,v),c=y;i(u,h,v)},m=({el:c,anchor:u})=>{let h;for(;c&&c!==u;)h=x(c),s(c),c=h;s(u)},M=(c,u,h,v,y,C,R,w,T)=>{R=R||u.type==="svg",c==null?q(u,h,v,y,C,R,w,T):A(c,u,y,C,R,w,T)},q=(c,u,h,v,y,C,R,w)=>{let T,b;const{type:H,props:I,shapeFlag:$,transition:K,dirs:Y}=c;if(T=c.el=r(c.type,C,I&&I.is,I),$&8?p(T,c.children):$&16&&P(c.children,T,null,v,y,C&&H!=="foreignObject",R,w),Y&&Pe(c,null,v,"created"),B(T,c,c.scopeId,R,v),I){for(const Z in I)Z!=="value"&&!Ft(Z)&&o(T,Z,null,I[Z],C,c.children,v,y,Ne);"value"in I&&o(T,"value",null,I.value),(b=I.onVnodeBeforeMount)&&we(b,v,c)}Y&&Pe(c,null,v,"beforeMount");const G=Ao(y,K);G&&K.beforeEnter(T),i(T,u,h),((b=I&&I.onVnodeMounted)||G||Y)&&he(()=>{b&&we(b,v,c),G&&K.enter(T),Y&&Pe(c,null,v,"mounted")},y)},B=(c,u,h,v,y)=>{if(h&&_(c,h),v)for(let C=0;C{for(let b=T;b{const w=u.el=c.el;let{patchFlag:T,dynamicChildren:b,dirs:H}=u;T|=c.patchFlag&16;const I=c.props||te,$=u.props||te;let K;h&&Ge(h,!1),(K=$.onVnodeBeforeUpdate)&&we(K,h,u,c),H&&Pe(u,c,h,"beforeUpdate"),h&&Ge(h,!0);const Y=y&&u.type!=="foreignObject";if(b?S(c.dynamicChildren,b,w,h,v,Y,C):R||k(c,u,w,null,h,v,Y,C,!1),T>0){if(T&16)V(w,u,I,$,h,v,y);else if(T&2&&I.class!==$.class&&o(w,"class",null,$.class,y),T&4&&o(w,"style",I.style,$.style,y),T&8){const G=u.dynamicProps;for(let Z=0;Z{K&&we(K,h,u,c),H&&Pe(u,c,h,"updated")},v)},S=(c,u,h,v,y,C,R)=>{for(let w=0;w{if(h!==v){if(h!==te)for(const w in h)!Ft(w)&&!(w in v)&&o(c,w,h[w],null,R,u.children,y,C,Ne);for(const w in v){if(Ft(w))continue;const T=v[w],b=h[w];T!==b&&w!=="value"&&o(c,w,b,T,R,u.children,y,C,Ne)}"value"in v&&o(c,"value",h.value,v.value)}},O=(c,u,h,v,y,C,R,w,T)=>{const b=u.el=c?c.el:l(""),H=u.anchor=c?c.anchor:l("");let{patchFlag:I,dynamicChildren:$,slotScopeIds:K}=u;K&&(w=w?w.concat(K):K),c==null?(i(b,h,v),i(H,h,v),P(u.children,h,H,y,C,R,w,T)):I>0&&I&64&&$&&c.dynamicChildren?(S(c.dynamicChildren,$,h,y,C,R,w),(u.key!=null||y&&u===y.subTree)&&So(c,u,!0)):k(c,u,h,H,y,C,R,w,T)},z=(c,u,h,v,y,C,R,w,T)=>{u.slotScopeIds=w,c==null?u.shapeFlag&512?y.ctx.activate(u,h,v,R,T):le(u,h,v,y,C,R,T):fe(c,u,T)},le=(c,u,h,v,y,C,R)=>{const w=c.component=Jl(c,v,y);if(zt(c)&&(w.ctx.renderer=pt),Xl(w),w.asyncDep){if(y&&y.registerDep(w,N),!c.el){const T=w.subTree=ie(be);U(null,T,u,h)}return}N(w,c,u,h,y,C,R)},fe=(c,u,h)=>{const v=u.component=c.component;if(rl(c,u,h))if(v.asyncDep&&!v.asyncResolved){J(v,u,h);return}else v.next=u,Gr(v.update),v.update();else u.el=c.el,v.vnode=u},N=(c,u,h,v,y,C,R)=>{const w=()=>{if(c.isMounted){let{next:H,bu:I,u:$,parent:K,vnode:Y}=c,G=H,Z;Ge(c,!1),H?(H.el=Y.el,J(c,H,R)):H=Y,I&&Bn(I),(Z=H.props&&H.props.onVnodeBeforeUpdate)&&we(Z,K,H,Y),Ge(c,!0);const oe=Kn(c),Te=c.subTree;c.subTree=oe,j(Te,oe,d(Te.el),Jt(Te),c,y,C),H.el=oe.el,G===null&&ll(c,oe.el),$&&he($,y),(Z=H.props&&H.props.onVnodeUpdated)&&he(()=>we(Z,K,H,Y),y)}else{let H;const{el:I,props:$}=u,{bm:K,m:Y,parent:G}=c,Z=yt(u);if(Ge(c,!1),K&&Bn(K),!Z&&(H=$&&$.onVnodeBeforeMount)&&we(H,G,u),Ge(c,!0),I&&Dn){const oe=()=>{c.subTree=Kn(c),Dn(I,c.subTree,c,y,null)};Z?u.type.__asyncLoader().then(()=>!c.isUnmounted&&oe()):oe()}else{const oe=c.subTree=Kn(c);j(null,oe,h,v,c,y,C),u.el=oe.el}if(Y&&he(Y,y),!Z&&(H=$&&$.onVnodeMounted)){const oe=u;he(()=>we(H,G,oe),y)}(u.shapeFlag&256||G&&yt(G.vnode)&&G.vnode.shapeFlag&256)&&c.a&&he(c.a,y),c.isMounted=!0,u=h=v=null}},T=c.effect=new $t(w,()=>Rn(b),c.scope),b=c.update=()=>T.run();b.id=c.uid,Ge(c,!0),b()},J=(c,u,h)=>{u.component=c;const v=c.vnode.props;c.vnode=u,c.next=null,Fl(c,u.props,v,h),kl(c,u.children,h),At(),Ji(c),St()},k=(c,u,h,v,y,C,R,w,T=!1)=>{const b=c&&c.children,H=c?c.shapeFlag:0,I=u.children,{patchFlag:$,shapeFlag:K}=u;if($>0){if($&128){Yt(b,I,h,v,y,C,R,w,T);return}else if($&256){Le(b,I,h,v,y,C,R,w,T);return}}K&8?(H&16&&Ne(b,y,C),I!==b&&p(h,I)):H&16?K&16?Yt(b,I,h,v,y,C,R,w,T):Ne(b,y,C,!0):(H&8&&p(h,""),K&16&&P(I,h,v,y,C,R,w,T))},Le=(c,u,h,v,y,C,R,w,T)=>{c=c||ht,u=u||ht;const b=c.length,H=u.length,I=Math.min(b,H);let $;for($=0;$H?Ne(c,y,C,!0,!1,I):P(u,h,v,y,C,R,w,T,I)},Yt=(c,u,h,v,y,C,R,w,T)=>{let b=0;const H=u.length;let I=c.length-1,$=H-1;for(;b<=I&&b<=$;){const K=c[b],Y=u[b]=T?qe(u[b]):Ae(u[b]);if(it(K,Y))j(K,Y,h,null,y,C,R,w,T);else break;b++}for(;b<=I&&b<=$;){const K=c[I],Y=u[$]=T?qe(u[$]):Ae(u[$]);if(it(K,Y))j(K,Y,h,null,y,C,R,w,T);else break;I--,$--}if(b>I){if(b<=$){const K=$+1,Y=K$)for(;b<=I;)Oe(c[b],y,C,!0),b++;else{const K=b,Y=b,G=new Map;for(b=Y;b<=$;b++){const ve=u[b]=T?qe(u[b]):Ae(u[b]);ve.key!=null&&G.set(ve.key,b)}let Z,oe=0;const Te=$-Y+1;let dt=!1,Hi=0;const Ot=new Array(Te);for(b=0;b=Te){Oe(ve,y,C,!0);continue}let Re;if(ve.key!=null)Re=G.get(ve.key);else for(Z=Y;Z<=$;Z++)if(Ot[Z-Y]===0&&it(ve,u[Z])){Re=Z;break}Re===void 0?Oe(ve,y,C,!0):(Ot[Re-Y]=b+1,Re>=Hi?Hi=Re:dt=!0,j(ve,u[Re],h,null,y,C,R,w,T),oe++)}const $i=dt?Bl(Ot):ht;for(Z=$i.length-1,b=Te-1;b>=0;b--){const ve=Y+b,Re=u[ve],Di=ve+1{const{el:C,type:R,transition:w,children:T,shapeFlag:b}=c;if(b&6){Ze(c.component.subTree,u,h,v);return}if(b&128){c.suspense.move(u,h,v);return}if(b&64){R.move(c,u,h,pt);return}if(R===ge){i(C,u,h);for(let I=0;Iw.enter(C),y);else{const{leave:I,delayLeave:$,afterLeave:K}=w,Y=()=>i(C,u,h),G=()=>{I(C,()=>{Y(),K&&K()})};$?$(C,Y,G):G()}else i(C,u,h)},Oe=(c,u,h,v=!1,y=!1)=>{const{type:C,props:R,ref:w,children:T,dynamicChildren:b,shapeFlag:H,patchFlag:I,dirs:$}=c;if(w!=null&&bn(w,null,h,c,!0),H&256){u.ctx.deactivate(c);return}const K=H&1&&$,Y=!yt(c);let G;if(Y&&(G=R&&R.onVnodeBeforeUnmount)&&we(G,u,c),H&6)ir(c.component,h,v);else{if(H&128){c.suspense.unmount(h,v);return}K&&Pe(c,null,u,"beforeUnmount"),H&64?c.type.remove(c,u,h,y,pt,v):b&&(C!==ge||I>0&&I&64)?Ne(b,u,h,!1,!0):(C===ge&&I&384||!y&&H&16)&&Ne(T,u,h),v&&Ni(c)}(Y&&(G=R&&R.onVnodeUnmounted)||K)&&he(()=>{G&&we(G,u,c),K&&Pe(c,null,u,"unmounted")},h)},Ni=c=>{const{type:u,el:h,anchor:v,transition:y}=c;if(u===ge){nr(h,v);return}if(u===kt){m(c);return}const C=()=>{s(h),y&&!y.persisted&&y.afterLeave&&y.afterLeave()};if(c.shapeFlag&1&&y&&!y.persisted){const{leave:R,delayLeave:w}=y,T=()=>R(h,C);w?w(c.el,C,T):T()}else C()},nr=(c,u)=>{let h;for(;c!==u;)h=x(c),s(c),c=h;s(u)},ir=(c,u,h)=>{const{bum:v,scope:y,update:C,subTree:R,um:w}=c;v&&Bn(v),y.stop(),C&&(C.active=!1,Oe(R,c,u,h)),w&&he(w,u),he(()=>{c.isUnmounted=!0},u),u&&u.pendingBranch&&!u.isUnmounted&&c.asyncDep&&!c.asyncResolved&&c.suspenseId===u.pendingId&&(u.deps--,u.deps===0&&u.resolve())},Ne=(c,u,h,v=!1,y=!1,C=0)=>{for(let R=C;Rc.shapeFlag&6?Jt(c.component.subTree):c.shapeFlag&128?c.suspense.next():x(c.anchor||c.el),ki=(c,u,h)=>{c==null?u._vnode&&Oe(u._vnode,null,null,!0):j(u._vnode||null,c,u,null,null,null,h),Ji(),gn(),u._vnode=c},pt={p:j,um:Oe,m:Ze,r:Ni,mt:le,mc:P,pc:k,pbc:S,n:Jt,o:e};let $n,Dn;return t&&([$n,Dn]=t(pt)),{render:ki,hydrate:$n,createApp:Pl(ki,$n)}}function Ge({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function Ao(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function So(e,t,n=!1){const i=e.children,s=t.children;if(D(i)&&D(s))for(let o=0;o>1,e[n[l]]0&&(t[i]=n[o-1]),n[o]=i)}}for(o=n.length,r=n[o-1];o-- >0;)n[o]=r,r=t[r];return n}const Ul=e=>e.__isTeleport,ge=Symbol.for("v-fgt"),wt=Symbol.for("v-txt"),be=Symbol.for("v-cmt"),kt=Symbol.for("v-stc"),Ht=[];let je=null;function jo(e=!1){Ht.push(je=e?null:[])}function Kl(){Ht.pop(),je=Ht[Ht.length-1]||null}let Kt=1;function ls(e){Kt+=e}function Oo(e){return e.dynamicChildren=Kt>0?je||ht:null,Kl(),Kt>0&&je&&je.push(e),e}function kc(e,t,n,i,s,o){return Oo(Mo(e,t,n,i,s,o,!0))}function Ro(e,t,n,i,s){return Oo(ie(e,t,n,i,s,!0))}function _n(e){return e?e.__v_isVNode===!0:!1}function it(e,t){return e.type===t.type&&e.key===t.key}const Nn="__vInternal",Po=({key:e})=>e??null,un=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?se(e)||ce(e)||W(e)?{i:pe,r:e,k:t,f:!!n}:e:null);function Mo(e,t=null,n=null,i=0,s=null,o=e===ge?0:1,r=!1,l=!1){const a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Po(t),ref:t&&un(t),scopeId:Mn,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:o,patchFlag:i,dynamicProps:s,dynamicChildren:null,appContext:null,ctx:pe};return l?(Mi(a,n),o&128&&e.normalize(a)):n&&(a.shapeFlag|=se(n)?8:16),Kt>0&&!r&&je&&(a.patchFlag>0||o&6)&&a.patchFlag!==32&&je.push(a),a}const ie=Wl;function Wl(e,t=null,n=null,i=0,s=null,o=!1){if((!e||e===lo)&&(e=be),_n(e)){const l=Qe(e,t,!0);return n&&Mi(l,n),Kt>0&&!o&&je&&(l.shapeFlag&6?je[je.indexOf(e)]=l:je.push(l)),l.patchFlag|=-2,l}if(ea(e)&&(e=e.__vccOpts),t){t=ql(t);let{class:l,style:a}=t;l&&!se(l)&&(t.class=vi(l)),ee(a)&&(Gs(a)&&!D(a)&&(a=re({},a)),t.style=xi(a))}const r=se(e)?1:al(e)?128:Ul(e)?64:ee(e)?4:W(e)?2:0;return Mo(e,t,n,i,s,r,o,!0)}function ql(e){return e?Gs(e)||Nn in e?re({},e):e:null}function Qe(e,t,n=!1){const{props:i,ref:s,patchFlag:o,children:r}=e,l=t?Vl(i||{},t):i;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&Po(l),ref:t&&t.ref?n&&s?D(s)?s.concat(un(t)):[s,un(t)]:un(t):s,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:r,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==ge?o===-1?16:o|16:o,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Qe(e.ssContent),ssFallback:e.ssFallback&&Qe(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function Io(e=" ",t=0){return ie(wt,null,e,t)}function Hc(e,t){const n=ie(kt,null,e);return n.staticCount=t,n}function $c(e="",t=!1){return t?(jo(),Ro(be,null,e)):ie(be,null,e)}function Ae(e){return e==null||typeof e=="boolean"?ie(be):D(e)?ie(ge,null,e.slice()):typeof e=="object"?qe(e):ie(wt,null,String(e))}function qe(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Qe(e)}function Mi(e,t){let n=0;const{shapeFlag:i}=e;if(t==null)t=null;else if(D(t))n=16;else if(typeof t=="object")if(i&65){const s=t.default;s&&(s._c&&(s._d=!1),Mi(e,s()),s._c&&(s._d=!0));return}else{n=32;const s=t._;!s&&!(Nn in t)?t._ctx=pe:s===3&&pe&&(pe.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else W(t)?(t={default:t,_ctx:pe},n=32):(t=String(t),i&64?(n=16,t=[Io(t)]):n=8);e.children=t,e.shapeFlag|=n}function Vl(...e){const t={};for(let n=0;nae||pe;let Ii,mt,as="__VUE_INSTANCE_SETTERS__";(mt=Gn()[as])||(mt=Gn()[as]=[]),mt.push(e=>ae=e),Ii=e=>{mt.length>1?mt.forEach(t=>t(e)):mt[0](e)};const Ct=e=>{Ii(e),e.scope.on()},lt=()=>{ae&&ae.scope.off(),Ii(null)};function Fo(e){return e.vnode.shapeFlag&4}let Et=!1;function Xl(e,t=!1){Et=t;const{props:n,children:i}=e.vnode,s=Fo(e);Il(e,n,s,t),Nl(e,i);const o=s?Ql(e,t):void 0;return Et=!1,o}function Ql(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=Lt(new Proxy(e.ctx,Cl));const{setup:i}=n;if(i){const s=e.setupContext=i.length>1?No(e):null;Ct(e),At();const o=ze(i,e,0,[e.props,s]);if(St(),lt(),Ns(o)){if(o.then(lt,lt),t)return o.then(r=>{cs(e,r,t)}).catch(r=>{Vt(r,e,0)});e.asyncDep=o}else cs(e,o,t)}else Lo(e,t)}function cs(e,t,n){W(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ee(t)&&(e.setupState=no(t)),Lo(e,n)}let fs;function Lo(e,t,n){const i=e.type;if(!e.render){if(!t&&fs&&!i.render){const s=i.template||Ri(e).template;if(s){const{isCustomElement:o,compilerOptions:r}=e.appContext.config,{delimiters:l,compilerOptions:a}=i,f=re(re({isCustomElement:o,delimiters:l},r),a);i.render=fs(s,f)}}e.render=i.render||Ie}{Ct(e),At();try{Tl(e)}finally{St(),lt()}}}function Zl(e){return e.attrsProxy||(e.attrsProxy=new Proxy(e.attrs,{get(t,n){return xe(e,"get","$attrs"),t[n]}}))}function No(e){const t=n=>{e.exposed=n||{}};return{get attrs(){return Zl(e)},slots:e.slots,emit:e.emit,expose:t}}function Hn(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(no(Lt(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Nt)return Nt[n](e)},has(t,n){return n in t||n in Nt}}))}function Gl(e,t=!0){return W(e)?e.displayName||e.name:e.name||t&&e.__name}function ea(e){return W(e)&&"__vccOpts"in e}const ne=(e,t)=>Xr(e,t,Et);function fi(e,t,n){const i=arguments.length;return i===2?ee(t)&&!D(t)?_n(t)?ie(e,null,[t]):ie(e,t):ie(e,null,t):(i>3?n=Array.prototype.slice.call(arguments,2):i===3&&_n(n)&&(n=[n]),ie(e,t,n))}const ta=Symbol.for("v-scx"),na=()=>bt(ta),ia="3.3.11",sa="http://www.w3.org/2000/svg",st=typeof document<"u"?document:null,us=st&&st.createElement("template"),oa={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,i)=>{const s=t?st.createElementNS(sa,e):st.createElement(e,n?{is:n}:void 0);return e==="select"&&i&&i.multiple!=null&&s.setAttribute("multiple",i.multiple),s},createText:e=>st.createTextNode(e),createComment:e=>st.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>st.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,i,s,o){const r=n?n.previousSibling:t.lastChild;if(s&&(s===o||s.nextSibling))for(;t.insertBefore(s.cloneNode(!0),n),!(s===o||!(s=s.nextSibling)););else{us.innerHTML=i?`${e}`:e;const l=us.content;if(i){const a=l.firstChild;for(;a.firstChild;)l.appendChild(a.firstChild);l.removeChild(a)}t.insertBefore(l,n)}return[r?r.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Ue="transition",Rt="animation",Wt=Symbol("_vtc"),ko=(e,{slots:t})=>fi(pl,ra(e),t);ko.displayName="Transition";const Ho={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};ko.props=re({},po,Ho);const et=(e,t=[])=>{D(e)?e.forEach(n=>n(...t)):e&&e(...t)},ps=e=>e?D(e)?e.some(t=>t.length>1):e.length>1:!1;function ra(e){const t={};for(const O in e)O in Ho||(t[O]=e[O]);if(e.css===!1)return t;const{name:n="v",type:i,duration:s,enterFromClass:o=`${n}-enter-from`,enterActiveClass:r=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:a=o,appearActiveClass:f=r,appearToClass:p=l,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:x=`${n}-leave-active`,leaveToClass:_=`${n}-leave-to`}=e,E=la(s),j=E&&E[0],F=E&&E[1],{onBeforeEnter:U,onEnter:L,onEnterCancelled:g,onLeave:m,onLeaveCancelled:M,onBeforeAppear:q=U,onAppear:B=L,onAppearCancelled:P=g}=t,A=(O,z,le)=>{tt(O,z?p:l),tt(O,z?f:r),le&&le()},S=(O,z)=>{O._isLeaving=!1,tt(O,d),tt(O,_),tt(O,x),z&&z()},V=O=>(z,le)=>{const fe=O?B:L,N=()=>A(z,O,le);et(fe,[z,N]),ds(()=>{tt(z,O?a:o),Ke(z,O?p:l),ps(fe)||ms(z,i,j,N)})};return re(t,{onBeforeEnter(O){et(U,[O]),Ke(O,o),Ke(O,r)},onBeforeAppear(O){et(q,[O]),Ke(O,a),Ke(O,f)},onEnter:V(!1),onAppear:V(!0),onLeave(O,z){O._isLeaving=!0;const le=()=>S(O,z);Ke(O,d),fa(),Ke(O,x),ds(()=>{O._isLeaving&&(tt(O,d),Ke(O,_),ps(m)||ms(O,i,F,le))}),et(m,[O,le])},onEnterCancelled(O){A(O,!1),et(g,[O])},onAppearCancelled(O){A(O,!0),et(P,[O])},onLeaveCancelled(O){S(O),et(M,[O])}})}function la(e){if(e==null)return null;if(ee(e))return[zn(e.enter),zn(e.leave)];{const t=zn(e);return[t,t]}}function zn(e){return fr(e)}function Ke(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[Wt]||(e[Wt]=new Set)).add(t)}function tt(e,t){t.split(/\s+/).forEach(i=>i&&e.classList.remove(i));const n=e[Wt];n&&(n.delete(t),n.size||(e[Wt]=void 0))}function ds(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let aa=0;function ms(e,t,n,i){const s=e._endId=++aa,o=()=>{s===e._endId&&i()};if(n)return setTimeout(o,n);const{type:r,timeout:l,propCount:a}=ca(e,t);if(!r)return i();const f=r+"end";let p=0;const d=()=>{e.removeEventListener(f,x),o()},x=_=>{_.target===e&&++p>=a&&d()};setTimeout(()=>{p(n[E]||"").split(", "),s=i(`${Ue}Delay`),o=i(`${Ue}Duration`),r=hs(s,o),l=i(`${Rt}Delay`),a=i(`${Rt}Duration`),f=hs(l,a);let p=null,d=0,x=0;t===Ue?r>0&&(p=Ue,d=r,x=o.length):t===Rt?f>0&&(p=Rt,d=f,x=a.length):(d=Math.max(r,f),p=d>0?r>f?Ue:Rt:null,x=p?p===Ue?o.length:a.length:0);const _=p===Ue&&/\b(transform|all)(,|$)/.test(i(`${Ue}Property`).toString());return{type:p,timeout:d,propCount:x,hasTransform:_}}function hs(e,t){for(;e.lengthgs(n)+gs(e[i])))}function gs(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function fa(){return document.body.offsetHeight}function ua(e,t,n){const i=e[Wt];i&&(t=(t?[t,...i]:[...i]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Fi=Symbol("_vod"),Dc={beforeMount(e,{value:t},{transition:n}){e[Fi]=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):Pt(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:i}){!t!=!n&&(i?t?(i.beforeEnter(e),Pt(e,!0),i.enter(e)):i.leave(e,()=>{Pt(e,!1)}):Pt(e,t))},beforeUnmount(e,{value:t}){Pt(e,t)}};function Pt(e,t){e.style.display=t?e[Fi]:"none"}function pa(e,t,n){const i=e.style,s=se(n);if(n&&!s){if(t&&!se(t))for(const o in t)n[o]==null&&ui(i,o,"");for(const o in n)ui(i,o,n[o])}else{const o=i.display;s?t!==n&&(i.cssText=n):t&&e.removeAttribute("style"),Fi in e&&(i.display=o)}}const xs=/\s*!important$/;function ui(e,t,n){if(D(n))n.forEach(i=>ui(e,t,i));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const i=da(e,t);xs.test(n)?e.setProperty(ut(i),n.replace(xs,""),"important"):e[i]=n}}const vs=["Webkit","Moz","ms"],Yn={};function da(e,t){const n=Yn[t];if(n)return n;let i=Fe(t);if(i!=="filter"&&i in e)return Yn[t]=i;i=Tn(i);for(let s=0;sJn||(ba.then(()=>Jn=0),Jn=Date.now());function wa(e,t){const n=i=>{if(!i._vts)i._vts=Date.now();else if(i._vts<=n.attached)return;Ee(Ca(i,n.value),t,5,[i])};return n.value=e,n.attached=_a(),n}function Ca(e,t){if(D(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(i=>s=>!s._stopped&&i&&i(s))}else return t}const ws=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,Ea=(e,t,n,i,s=!1,o,r,l,a)=>{t==="class"?ua(e,i,s):t==="style"?pa(e,n,i):qt(t)?mi(t)||va(e,t,n,i,r):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):Ta(e,t,i,s))?ha(e,t,i,o,r,l,a):(t==="true-value"?e._trueValue=i:t==="false-value"&&(e._falseValue=i),ma(e,t,i,s))};function Ta(e,t,n,i){if(i)return!!(t==="innerHTML"||t==="textContent"||t in e&&ws(t)&&W(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const s=e.tagName;if(s==="IMG"||s==="VIDEO"||s==="CANVAS"||s==="SOURCE")return!1}return ws(t)&&se(n)?!1:t in e}const Aa=["ctrl","shift","alt","meta"],Sa={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>Aa.some(n=>e[`${n}Key`]&&!t.includes(n))},Bc=(e,t)=>e._withMods||(e._withMods=(n,...i)=>{for(let s=0;se._withKeys||(e._withKeys=n=>{if(!("key"in n))return;const i=ut(n.key);if(t.some(s=>s===i||ja[s]===i))return e(n)}),Oa=re({patchProp:Ea},oa);let Xn,Cs=!1;function Ra(){return Xn=Cs?Xn:$l(Oa),Cs=!0,Xn}const Kc=(...e)=>{const t=Ra().createApp(...e),{mount:n}=t;return t.mount=i=>{const s=Pa(i);if(s)return n(s,!0,s instanceof SVGElement)},t};function Pa(e){return se(e)?document.querySelector(e):e}const Wc=(e,t)=>{const n=e.__vccOpts||e;for(const[i,s]of t)n[i]=s;return n},qc="/Coalesce/coalesce-icon-color.svg",Vc="/Coalesce/net-logo.svg",zc="/Coalesce/ef-logo.svg",Yc="/Coalesce/ts-logo-512.svg",Jc="/Coalesce/vue-logo.svg",Xc="/Coalesce/vite-logo.svg",Ma="modulepreload",Ia=function(e){return"/Coalesce/"+e},Es={},Qc=function(t,n,i){let s=Promise.resolve();if(n&&n.length>0){const o=document.getElementsByTagName("link");s=Promise.all(n.map(r=>{if(r=Ia(r),r in Es)return;Es[r]=!0;const l=r.endsWith(".css"),a=l?'[rel="stylesheet"]':"";if(!!i)for(let d=o.length-1;d>=0;d--){const x=o[d];if(x.href===r&&(!l||x.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${r}"]${a}`))return;const p=document.createElement("link");if(p.rel=l?"stylesheet":Ma,l||(p.as="script",p.crossOrigin=""),p.href=r,document.head.appendChild(p),l)return new Promise((d,x)=>{p.addEventListener("load",d),p.addEventListener("error",()=>x(new Error(`Unable to preload CSS for ${r}`)))})}))}return s.then(()=>t()).catch(o=>{const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=o,window.dispatchEvent(r),!r.defaultPrevented)throw o})},Fa=window.__VP_SITE_DATA__;function Li(e){return Us()?(vr(e),!0):!1}function Je(e){return typeof e=="function"?e():Ai(e)}function Zc(e,t){const n=(t==null?void 0:t.computedGetter)===!1?Ai:Je;return function(...i){return ne(()=>e.apply(this,i.map(s=>n(s))))}}const $o=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const La=Object.prototype.toString,Na=e=>La.call(e)==="[object Object]",Do=()=>{},Ts=ka();function ka(){var e,t;return $o&&((e=window==null?void 0:window.navigator)==null?void 0:e.userAgent)&&(/iP(ad|hone|od)/.test(window.navigator.userAgent)||((t=window==null?void 0:window.navigator)==null?void 0:t.maxTouchPoints)>2&&/iPad|Macintosh/.test(window==null?void 0:window.navigator.userAgent))}function Ha(e,t){function n(...i){return new Promise((s,o)=>{Promise.resolve(e(()=>t.apply(this,i),{fn:t,thisArg:this,args:i})).then(s).catch(o)})}return n}const Bo=e=>e();function $a(e=Bo){const t=ue(!0);function n(){t.value=!1}function i(){t.value=!0}const s=(...o)=>{t.value&&e(...o)};return{isActive:jn(t),pause:n,resume:i,eventFilter:s}}function Da(e){return e||kn()}function Uo(...e){if(e.length!==1)return zr(...e);const t=e[0];return typeof t=="function"?jn(Wr(()=>({get:t,set:Do}))):ue(t)}function Ba(e,t,n={}){const{eventFilter:i=Bo,...s}=n;return Ye(e,Ha(i,t),s)}function Ua(e,t,n={}){const{eventFilter:i,...s}=n,{eventFilter:o,pause:r,resume:l,isActive:a}=$a(i);return{stop:Ba(e,t,{...s,eventFilter:o}),pause:r,resume:l,isActive:a}}function Ko(e,t=!0,n){const i=Da(n);i?jt(e,i):t?e():On(e)}function Wo(e){var t;const n=Je(e);return(t=n==null?void 0:n.$el)!=null?t:n}const ft=$o?window:void 0;function wn(...e){let t,n,i,s;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,i,s]=e,t=ft):[t,n,i,s]=e,!t)return Do;Array.isArray(n)||(n=[n]),Array.isArray(i)||(i=[i]);const o=[],r=()=>{o.forEach(p=>p()),o.length=0},l=(p,d,x,_)=>(p.addEventListener(d,x,_),()=>p.removeEventListener(d,x,_)),a=Ye(()=>[Wo(t),Je(s)],([p,d])=>{if(r(),!p)return;const x=Na(d)?{...d}:d;o.push(...n.flatMap(_=>i.map(E=>l(p,_,E,x))))},{immediate:!0,flush:"post"}),f=()=>{a(),r()};return Li(f),f}function Ka(){const e=ue(!1);return kn()&&jt(()=>{e.value=!0}),e}function Wa(e){const t=Ka();return ne(()=>(t.value,!!e()))}function qa(e,t={}){const{window:n=ft}=t,i=Wa(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let s;const o=ue(!1),r=f=>{o.value=f.matches},l=()=>{s&&("removeEventListener"in s?s.removeEventListener("change",r):s.removeListener(r))},a=fo(()=>{i.value&&(l(),s=n.matchMedia(Je(e)),"addEventListener"in s?s.addEventListener("change",r):s.addListener(r),o.value=s.matches)});return Li(()=>{a(),l(),s=void 0}),o}const rn=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},ln="__vueuse_ssr_handlers__",Va=za();function za(){return ln in rn||(rn[ln]=rn[ln]||{}),rn[ln]}function qo(e,t){return Va[e]||t}function Ya(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const Ja={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},As="vueuse-storage";function Xa(e,t,n,i={}){var s;const{flush:o="pre",deep:r=!0,listenToStorageChanges:l=!0,writeDefaults:a=!0,mergeDefaults:f=!1,shallow:p,window:d=ft,eventFilter:x,onError:_=A=>{console.error(A)},initOnMounted:E}=i,j=(p?eo:ue)(typeof t=="function"?t():t);if(!n)try{n=qo("getDefaultStorage",()=>{var A;return(A=ft)==null?void 0:A.localStorage})()}catch(A){_(A)}if(!n)return j;const F=Je(t),U=Ya(F),L=(s=i.serializer)!=null?s:Ja[U],{pause:g,resume:m}=Ua(j,()=>M(j.value),{flush:o,deep:r,eventFilter:x});return d&&l&&Ko(()=>{wn(d,"storage",P),wn(d,As,B),E&&P()}),E||P(),j;function M(A){try{if(A==null)n.removeItem(e);else{const S=L.write(A),V=n.getItem(e);V!==S&&(n.setItem(e,S),d&&d.dispatchEvent(new CustomEvent(As,{detail:{key:e,oldValue:V,newValue:S,storageArea:n}})))}}catch(S){_(S)}}function q(A){const S=A?A.newValue:n.getItem(e);if(S==null)return a&&F!=null&&n.setItem(e,L.write(F)),F;if(!A&&f){const V=L.read(S);return typeof f=="function"?f(V,F):U==="object"&&!Array.isArray(V)?{...F,...V}:V}else return typeof S!="string"?S:L.read(S)}function B(A){P(A.detail)}function P(A){if(!(A&&A.storageArea!==n)){if(A&&A.key==null){j.value=F;return}if(!(A&&A.key!==e)){g();try{(A==null?void 0:A.newValue)!==L.write(j.value)&&(j.value=q(A))}catch(S){_(S)}finally{A?On(m):m()}}}}}function Vo(e){return qa("(prefers-color-scheme: dark)",e)}function Qa(e={}){const{selector:t="html",attribute:n="class",initialValue:i="auto",window:s=ft,storage:o,storageKey:r="vueuse-color-scheme",listenToStorageChanges:l=!0,storageRef:a,emitAuto:f,disableTransition:p=!0}=e,d={auto:"",light:"light",dark:"dark",...e.modes||{}},x=Vo({window:s}),_=ne(()=>x.value?"dark":"light"),E=a||(r==null?Uo(i):Xa(r,i,o,{window:s,listenToStorageChanges:l})),j=ne(()=>E.value==="auto"?_.value:E.value),F=qo("updateHTMLAttrs",(m,M,q)=>{const B=typeof m=="string"?s==null?void 0:s.document.querySelector(m):Wo(m);if(!B)return;let P;if(p){P=s.document.createElement("style");const A="*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}";P.appendChild(document.createTextNode(A)),s.document.head.appendChild(P)}if(M==="class"){const A=q.split(/\s/g);Object.values(d).flatMap(S=>(S||"").split(/\s/g)).filter(Boolean).forEach(S=>{A.includes(S)?B.classList.add(S):B.classList.remove(S)})}else B.setAttribute(M,q);p&&(s.getComputedStyle(P).opacity,document.head.removeChild(P))});function U(m){var M;F(t,n,(M=d[m])!=null?M:m)}function L(m){e.onChanged?e.onChanged(m,U):U(m)}Ye(j,L,{flush:"post",immediate:!0}),Ko(()=>L(j.value));const g=ne({get(){return f?E.value:j.value},set(m){E.value=m}});try{return Object.assign(g,{store:E,system:_,state:j})}catch{return g}}function Za(e={}){const{valueDark:t="dark",valueLight:n="",window:i=ft}=e,s=Qa({...e,onChanged:(l,a)=>{var f;e.onChanged?(f=e.onChanged)==null||f.call(e,l==="dark",a,l):a(l)},modes:{dark:t,light:n}}),o=ne(()=>s.system?s.system.value:Vo({window:i}).value?"dark":"light");return ne({get(){return s.value==="dark"},set(l){const a=l?"dark":"light";o.value===a?s.value="auto":s.value=a}})}function Qn(e){return typeof Window<"u"&&e instanceof Window?e.document.documentElement:typeof Document<"u"&&e instanceof Document?e.documentElement:e}function zo(e){const t=window.getComputedStyle(e);if(t.overflowX==="scroll"||t.overflowY==="scroll"||t.overflowX==="auto"&&e.clientWidth1?!0:(t.preventDefault&&t.preventDefault(),!1)}const an=new WeakMap;function Gc(e,t=!1){const n=ue(t);let i=null,s;Ye(Uo(e),l=>{const a=Qn(Je(l));if(a){const f=a;an.get(f)||an.set(f,s),n.value&&(f.style.overflow="hidden")}},{immediate:!0});const o=()=>{const l=Qn(Je(e));!l||n.value||(Ts&&(i=wn(l,"touchmove",a=>{Ga(a)},{passive:!1})),l.style.overflow="hidden",n.value=!0)},r=()=>{var l;const a=Qn(Je(e));!a||!n.value||(Ts&&(i==null||i()),a.style.overflow=(l=an.get(a))!=null?l:"",an.delete(a),n.value=!1)};return Li(r),ne({get(){return n.value},set(l){l?o():r()}})}function ef(e={}){const{window:t=ft,behavior:n="auto"}=e;if(!t)return{x:ue(0),y:ue(0)};const i=ue(t.scrollX),s=ue(t.scrollY),o=ne({get(){return i.value},set(l){scrollTo({left:l,behavior:n})}}),r=ne({get(){return s.value},set(l){scrollTo({top:l,behavior:n})}});return wn(t,"scroll",()=>{i.value=t.scrollX,s.value=t.scrollY},{capture:!1,passive:!0}),{x:o,y:r}}const Yo=/^(?:[a-z]+:|\/\/)/i,ec="vitepress-theme-appearance",Jo=/#.*$/,tc=/(index)?\.(md|html)$/,Ce=typeof document<"u",Xo={relativePath:"",filePath:"",title:"404",description:"Not Found",headers:[],frontmatter:{sidebar:!1,layout:"page"},lastUpdated:0,isNotFound:!0};function nc(e,t,n=!1){if(t===void 0)return!1;if(e=Ss(`/${e}`),n)return new RegExp(t).test(e);if(Ss(t)!==e)return!1;const i=t.match(Jo);return i?(Ce?location.hash:"")===i[0]:!0}function Ss(e){return decodeURI(e).replace(Jo,"").replace(tc,"")}function ic(e){return Yo.test(e)}function sc(e,t){var i,s,o,r,l,a,f;const n=Object.keys(e.locales).find(p=>p!=="root"&&!ic(p)&&nc(t,`/${p}/`,!0))||"root";return Object.assign({},e,{localeIndex:n,lang:((i=e.locales[n])==null?void 0:i.lang)??e.lang,dir:((s=e.locales[n])==null?void 0:s.dir)??e.dir,title:((o=e.locales[n])==null?void 0:o.title)??e.title,titleTemplate:((r=e.locales[n])==null?void 0:r.titleTemplate)??e.titleTemplate,description:((l=e.locales[n])==null?void 0:l.description)??e.description,head:Zo(e.head,((a=e.locales[n])==null?void 0:a.head)??[]),themeConfig:{...e.themeConfig,...(f=e.locales[n])==null?void 0:f.themeConfig}})}function Qo(e,t){const n=t.title||e.title,i=t.titleTemplate??e.titleTemplate;if(typeof i=="string"&&i.includes(":title"))return i.replace(/:title/g,n);const s=oc(e.title,i);return`${n}${s}`}function oc(e,t){return t===!1?"":t===!0||t===void 0?` | ${e}`:e===t?"":` | ${t}`}function rc(e,t){const[n,i]=t;if(n!=="meta")return!1;const s=Object.entries(i)[0];return s==null?!1:e.some(([o,r])=>o===n&&r[s[0]]===s[1])}function Zo(e,t){return[...e.filter(n=>!rc(t,n)),...t]}const lc=/[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g,ac=/^[a-z]:/i;function js(e){const t=ac.exec(e),n=t?t[0]:"";return n+e.slice(n.length).replace(lc,"_").replace(/(^|\/)_+(?=[^/]*$)/,"$1")}const cc=Symbol(),at=eo(Fa);function tf(e){const t=ne(()=>sc(at.value,e.data.relativePath)),n=t.value.appearance,i=n==="force-dark"?ue(!0):n?Za({storageKey:ec,initialValue:()=>typeof n=="string"?n:"auto",...typeof n=="object"?n:{}}):ue(!1);return{site:t,theme:ne(()=>t.value.themeConfig),page:ne(()=>e.data),frontmatter:ne(()=>e.data.frontmatter),params:ne(()=>e.data.params),lang:ne(()=>t.value.lang),dir:ne(()=>t.value.dir),localeIndex:ne(()=>t.value.localeIndex||"root"),title:ne(()=>Qo(t.value,e.data)),description:ne(()=>e.data.description||t.value.description),isDark:i}}function fc(){const e=bt(cc);if(!e)throw new Error("vitepress data not properly injected in app");return e}const uc={ez:"application/andrew-inset",aw:"application/applixware",atom:"application/atom+xml",atomcat:"application/atomcat+xml",atomdeleted:"application/atomdeleted+xml",atomsvc:"application/atomsvc+xml",dwd:"application/atsc-dwd+xml",held:"application/atsc-held+xml",rsat:"application/atsc-rsat+xml",bdoc:"application/bdoc",xcs:"application/calendar+xml",ccxml:"application/ccxml+xml",cdfx:"application/cdfx+xml",cdmia:"application/cdmi-capability",cdmic:"application/cdmi-container",cdmid:"application/cdmi-domain",cdmio:"application/cdmi-object",cdmiq:"application/cdmi-queue",cu:"application/cu-seeme",mpd:"application/dash+xml",davmount:"application/davmount+xml",dbk:"application/docbook+xml",dssc:"application/dssc+der",xdssc:"application/dssc+xml",es:"application/ecmascript",ecma:"application/ecmascript",emma:"application/emma+xml",emotionml:"application/emotionml+xml",epub:"application/epub+zip",exi:"application/exi",fdt:"application/fdt+xml",pfr:"application/font-tdpfr",geojson:"application/geo+json",gml:"application/gml+xml",gpx:"application/gpx+xml",gxf:"application/gxf",gz:"application/gzip",hjson:"application/hjson",stk:"application/hyperstudio",ink:"application/inkml+xml",inkml:"application/inkml+xml",ipfix:"application/ipfix",its:"application/its+xml",jar:"application/java-archive",war:"application/java-archive",ear:"application/java-archive",ser:"application/java-serialized-object",class:"application/java-vm",js:"application/javascript",mjs:"application/javascript",json:"application/json",map:"application/json",json5:"application/json5",jsonml:"application/jsonml+json",jsonld:"application/ld+json",lgr:"application/lgr+xml",lostxml:"application/lost+xml",hqx:"application/mac-binhex40",cpt:"application/mac-compactpro",mads:"application/mads+xml",webmanifest:"application/manifest+json",mrc:"application/marc",mrcx:"application/marcxml+xml",ma:"application/mathematica",nb:"application/mathematica",mb:"application/mathematica",mathml:"application/mathml+xml",mbox:"application/mbox",mscml:"application/mediaservercontrol+xml",metalink:"application/metalink+xml",meta4:"application/metalink4+xml",mets:"application/mets+xml",maei:"application/mmt-aei+xml",musd:"application/mmt-usd+xml",mods:"application/mods+xml",m21:"application/mp21",mp21:"application/mp21",mp4s:"application/mp4",m4p:"application/mp4",doc:"application/msword",dot:"application/msword",mxf:"application/mxf",nq:"application/n-quads",nt:"application/n-triples",cjs:"application/node",bin:"application/octet-stream",dms:"application/octet-stream",lrf:"application/octet-stream",mar:"application/octet-stream",so:"application/octet-stream",dist:"application/octet-stream",distz:"application/octet-stream",pkg:"application/octet-stream",bpk:"application/octet-stream",dump:"application/octet-stream",elc:"application/octet-stream",deploy:"application/octet-stream",exe:"application/octet-stream",dll:"application/octet-stream",deb:"application/octet-stream",dmg:"application/octet-stream",iso:"application/octet-stream",img:"application/octet-stream",msi:"application/octet-stream",msp:"application/octet-stream",msm:"application/octet-stream",buffer:"application/octet-stream",oda:"application/oda",opf:"application/oebps-package+xml",ogx:"application/ogg",omdoc:"application/omdoc+xml",onetoc:"application/onenote",onetoc2:"application/onenote",onetmp:"application/onenote",onepkg:"application/onenote",oxps:"application/oxps",relo:"application/p2p-overlay+xml",xer:"application/patch-ops-error+xml",pdf:"application/pdf",pgp:"application/pgp-encrypted",asc:"application/pgp-signature",sig:"application/pgp-signature",prf:"application/pics-rules",p10:"application/pkcs10",p7m:"application/pkcs7-mime",p7c:"application/pkcs7-mime",p7s:"application/pkcs7-signature",p8:"application/pkcs8",ac:"application/pkix-attr-cert",cer:"application/pkix-cert",crl:"application/pkix-crl",pkipath:"application/pkix-pkipath",pki:"application/pkixcmp",pls:"application/pls+xml",ai:"application/postscript",eps:"application/postscript",ps:"application/postscript",provx:"application/provenance+xml",cww:"application/prs.cww",pskcxml:"application/pskc+xml",raml:"application/raml+yaml",rdf:"application/rdf+xml",owl:"application/rdf+xml",rif:"application/reginfo+xml",rnc:"application/relax-ng-compact-syntax",rl:"application/resource-lists+xml",rld:"application/resource-lists-diff+xml",rs:"application/rls-services+xml",rapd:"application/route-apd+xml",sls:"application/route-s-tsid+xml",rusd:"application/route-usd+xml",gbr:"application/rpki-ghostbusters",mft:"application/rpki-manifest",roa:"application/rpki-roa",rsd:"application/rsd+xml",rss:"application/rss+xml",rtf:"application/rtf",sbml:"application/sbml+xml",scq:"application/scvp-cv-request",scs:"application/scvp-cv-response",spq:"application/scvp-vp-request",spp:"application/scvp-vp-response",sdp:"application/sdp",senmlx:"application/senml+xml",sensmlx:"application/sensml+xml",setpay:"application/set-payment-initiation",setreg:"application/set-registration-initiation",shf:"application/shf+xml",siv:"application/sieve",sieve:"application/sieve",smi:"application/smil+xml",smil:"application/smil+xml",rq:"application/sparql-query",srx:"application/sparql-results+xml",gram:"application/srgs",grxml:"application/srgs+xml",sru:"application/sru+xml",ssdl:"application/ssdl+xml",ssml:"application/ssml+xml",swidtag:"application/swid+xml",tei:"application/tei+xml",teicorpus:"application/tei+xml",tfi:"application/thraud+xml",tsd:"application/timestamped-data",toml:"application/toml",trig:"application/trig",ttml:"application/ttml+xml",ubj:"application/ubjson",rsheet:"application/urc-ressheet+xml",td:"application/urc-targetdesc+xml",vxml:"application/voicexml+xml",wasm:"application/wasm",wgt:"application/widget",hlp:"application/winhlp",wsdl:"application/wsdl+xml",wspolicy:"application/wspolicy+xml",xaml:"application/xaml+xml",xav:"application/xcap-att+xml",xca:"application/xcap-caps+xml",xdf:"application/xcap-diff+xml",xel:"application/xcap-el+xml",xns:"application/xcap-ns+xml",xenc:"application/xenc+xml",xhtml:"application/xhtml+xml",xht:"application/xhtml+xml",xlf:"application/xliff+xml",xml:"application/xml",xsl:"application/xml",xsd:"application/xml",rng:"application/xml",dtd:"application/xml-dtd",xop:"application/xop+xml",xpl:"application/xproc+xml",xslt:"application/xml",xspf:"application/xspf+xml",mxml:"application/xv+xml",xhvml:"application/xv+xml",xvml:"application/xv+xml",xvm:"application/xv+xml",yang:"application/yang",yin:"application/yin+xml",zip:"application/zip","3gpp":"video/3gpp",adp:"audio/adpcm",amr:"audio/amr",au:"audio/basic",snd:"audio/basic",mid:"audio/midi",midi:"audio/midi",kar:"audio/midi",rmi:"audio/midi",mxmf:"audio/mobile-xmf",mp3:"audio/mpeg",m4a:"audio/mp4",mp4a:"audio/mp4",mpga:"audio/mpeg",mp2:"audio/mpeg",mp2a:"audio/mpeg",m2a:"audio/mpeg",m3a:"audio/mpeg",oga:"audio/ogg",ogg:"audio/ogg",spx:"audio/ogg",opus:"audio/ogg",s3m:"audio/s3m",sil:"audio/silk",wav:"audio/wav",weba:"audio/webm",xm:"audio/xm",ttc:"font/collection",otf:"font/otf",ttf:"font/ttf",woff:"font/woff",woff2:"font/woff2",exr:"image/aces",apng:"image/apng",avif:"image/avif",bmp:"image/bmp",cgm:"image/cgm",drle:"image/dicom-rle",emf:"image/emf",fits:"image/fits",g3:"image/g3fax",gif:"image/gif",heic:"image/heic",heics:"image/heic-sequence",heif:"image/heif",heifs:"image/heif-sequence",hej2:"image/hej2k",hsj2:"image/hsj2",ief:"image/ief",jls:"image/jls",jp2:"image/jp2",jpg2:"image/jp2",jpeg:"image/jpeg",jpg:"image/jpeg",jpe:"image/jpeg",jph:"image/jph",jhc:"image/jphc",jpm:"image/jpm",jpx:"image/jpx",jpf:"image/jpx",jxr:"image/jxr",jxra:"image/jxra",jxrs:"image/jxrs",jxs:"image/jxs",jxsc:"image/jxsc",jxsi:"image/jxsi",jxss:"image/jxss",ktx:"image/ktx",ktx2:"image/ktx2",png:"image/png",btif:"image/prs.btif",pti:"image/prs.pti",sgi:"image/sgi",svg:"image/svg+xml",svgz:"image/svg+xml",t38:"image/t38",tif:"image/tiff",tiff:"image/tiff",tfx:"image/tiff-fx",webp:"image/webp",wmf:"image/wmf","disposition-notification":"message/disposition-notification",u8msg:"message/global",u8dsn:"message/global-delivery-status",u8mdn:"message/global-disposition-notification",u8hdr:"message/global-headers",eml:"message/rfc822",mime:"message/rfc822","3mf":"model/3mf",gltf:"model/gltf+json",glb:"model/gltf-binary",igs:"model/iges",iges:"model/iges",msh:"model/mesh",mesh:"model/mesh",silo:"model/mesh",mtl:"model/mtl",obj:"model/obj",stpz:"model/step+zip",stpxz:"model/step-xml+zip",stl:"model/stl",wrl:"model/vrml",vrml:"model/vrml",x3db:"model/x3d+fastinfoset",x3dbz:"model/x3d+binary",x3dv:"model/x3d-vrml",x3dvz:"model/x3d+vrml",x3d:"model/x3d+xml",x3dz:"model/x3d+xml",appcache:"text/cache-manifest",manifest:"text/cache-manifest",ics:"text/calendar",ifb:"text/calendar",coffee:"text/coffeescript",litcoffee:"text/coffeescript",css:"text/css",csv:"text/csv",html:"text/html",htm:"text/html",shtml:"text/html",jade:"text/jade",jsx:"text/jsx",less:"text/less",markdown:"text/markdown",md:"text/markdown",mml:"text/mathml",mdx:"text/mdx",n3:"text/n3",txt:"text/plain",text:"text/plain",conf:"text/plain",def:"text/plain",list:"text/plain",log:"text/plain",in:"text/plain",ini:"text/plain",dsc:"text/prs.lines.tag",rtx:"text/richtext",sgml:"text/sgml",sgm:"text/sgml",shex:"text/shex",slim:"text/slim",slm:"text/slim",spdx:"text/spdx",stylus:"text/stylus",styl:"text/stylus",tsv:"text/tab-separated-values",t:"text/troff",tr:"text/troff",roff:"text/troff",man:"text/troff",me:"text/troff",ms:"text/troff",ttl:"text/turtle",uri:"text/uri-list",uris:"text/uri-list",urls:"text/uri-list",vcard:"text/vcard",vtt:"text/vtt",yaml:"text/yaml",yml:"text/yaml","3gp":"video/3gpp","3g2":"video/3gpp2",h261:"video/h261",h263:"video/h263",h264:"video/h264",m4s:"video/iso.segment",jpgv:"video/jpeg",jpgm:"image/jpm",mj2:"video/mj2",mjp2:"video/mj2",ts:"video/mp2t",mp4:"video/mp4",mp4v:"video/mp4",mpg4:"video/mp4",mpeg:"video/mpeg",mpg:"video/mpeg",mpe:"video/mpeg",m1v:"video/mpeg",m2v:"video/mpeg",ogv:"video/ogg",qt:"video/quicktime",mov:"video/quicktime",webm:"video/webm"};function pc(e){let t=(""+e).trim().toLowerCase(),n=t.lastIndexOf(".");return uc[~n?t.substring(++n):t]}function dc(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function Os(e){return Yo.test(e)||!e.startsWith("/")?e:dc(at.value.base,e)}function mc(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t=t.replace(/\/$/,"/index"),Ce){const n="/Coalesce/";t=js(t.slice(n.length).replace(/\//g,"_")||"index")+".md";let i=__VP_HASH_MAP__[t.toLowerCase()];if(i||(t=t.endsWith("_index.md")?t.slice(0,-9)+".md":t.slice(0,-3)+"_index.md",i=__VP_HASH_MAP__[t.toLowerCase()]),!i)return null;t=`${n}assets/${t}.${i}.js`}else t=`./${js(t.slice(1).replace(/\//g,"_"))}.md.js`;return t}let pn=[];function nf(e){pn.push(e),Ln(()=>{pn=pn.filter(t=>t!==e)})}const hc=Symbol(),Go="http://a.com",gc=()=>({path:"/",component:null,data:Xo});function sf(e,t){const n=Sn(gc()),i={route:n,go:s};async function s(l=Ce?location.href:"/"){var a,f;l=pi(l),await((a=i.onBeforeRouteChange)==null?void 0:a.call(i,l))!==!1&&(Ms(l),await r(l),await((f=i.onAfterRouteChanged)==null?void 0:f.call(i,l)))}let o=null;async function r(l,a=0,f=!1){var x;if(await((x=i.onBeforePageLoad)==null?void 0:x.call(i,l))===!1)return;const p=new URL(l,Go),d=o=p.pathname;try{let _=await e(d);if(!_)throw new Error(`Page not found: ${d}`);if(o===d){o=null;const{default:E,__pageData:j}=_;if(!E)throw new Error(`Invalid route component: ${E}`);n.path=Ce?d:Os(d),n.component=Lt(E),n.data=Lt(j),Ce&&On(()=>{let F=at.value.base+j.relativePath.replace(/(?:(^|\/)index)?\.md$/,"$1");if(!at.value.cleanUrls&&!F.endsWith("/")&&(F+=".html"),F!==p.pathname&&(p.pathname=F,l=F+p.search+p.hash,history.replaceState(null,"",l)),p.hash&&!a){let U=null;try{U=document.getElementById(decodeURIComponent(p.hash).slice(1))}catch(L){console.warn(L)}if(U){Rs(U,p.hash);return}}window.scrollTo(0,a)})}}catch(_){if(!/fetch|Page not found/.test(_.message)&&!/^\/404(\.html|\/)?$/.test(l)&&console.error(_),!f)try{const E=await fetch(at.value.base+"hashmap.json");window.__VP_HASH_MAP__=await E.json(),await r(l,a,!0);return}catch{}o===d&&(o=null,n.path=Ce?d:Os(d),n.component=t?Lt(t):null,n.data=Xo)}}return Ce&&(window.addEventListener("click",l=>{if(l.target.closest("button"))return;const f=l.target.closest("a");if(f&&!f.closest(".vp-raw")&&(f instanceof SVGElement||!f.download)){const{target:p}=f,{href:d,origin:x,pathname:_,hash:E,search:j}=new URL(f.href instanceof SVGAnimatedString?f.href.animVal:f.href,f.baseURI),F=window.location,U=pc(_);!l.ctrlKey&&!l.shiftKey&&!l.altKey&&!l.metaKey&&!p&&x===F.origin&&(!U||U==="text/html")&&(l.preventDefault(),_===F.pathname&&j===F.search?(E!==F.hash&&(history.pushState(null,"",E),window.dispatchEvent(new Event("hashchange"))),E?Rs(f,E,f.classList.contains("header-anchor")):(Ms(d),window.scrollTo(0,0))):s(d))}},{capture:!0}),window.addEventListener("popstate",async l=>{var a;await r(pi(location.href),l.state&&l.state.scrollPosition||0),(a=i.onAfterRouteChanged)==null||a.call(i,location.href)}),window.addEventListener("hashchange",l=>{l.preventDefault()})),i}function xc(){const e=bt(hc);if(!e)throw new Error("useRouter() is called without provider.");return e}function er(){return xc().route}function Rs(e,t,n=!1){let i=null;try{i=e.classList.contains("header-anchor")?e:document.getElementById(decodeURIComponent(t).slice(1))}catch(s){console.warn(s)}if(i){let s=function(){!n||Math.abs(f-window.scrollY)>window.innerHeight?window.scrollTo(0,f):window.scrollTo({left:0,top:f,behavior:"smooth"})},o=at.value.scrollOffset,r=0,l=24;if(typeof o=="object"&&"padding"in o&&(l=o.padding,o=o.selector),typeof o=="number")r=o;else if(typeof o=="string")r=Ps(o,l);else if(Array.isArray(o))for(const p of o){const d=Ps(p,l);if(d){r=d;break}}const a=parseInt(window.getComputedStyle(i).paddingTop,10),f=window.scrollY+i.getBoundingClientRect().top-r+a;requestAnimationFrame(s)}}function Ps(e,t){const n=document.querySelector(e);if(!n)return 0;const i=n.getBoundingClientRect().bottom;return i<0?0:i+t}function Ms(e){Ce&&e!==pi(location.href)&&(history.replaceState({scrollPosition:window.scrollY},document.title),history.pushState(null,"",e))}function pi(e){const t=new URL(e,Go);return t.pathname=t.pathname.replace(/(^|\/)index(\.html)?$/,"$1"),at.value.cleanUrls?t.pathname=t.pathname.replace(/\.html$/,""):!t.pathname.endsWith("/")&&!t.pathname.endsWith(".html")&&(t.pathname+=".html"),t.pathname+t.search+t.hash}const Is=()=>pn.forEach(e=>e()),of=Oi({name:"VitePressContent",props:{as:{type:[Object,String],default:"div"}},setup(e){const t=er(),{site:n}=fc();return()=>fi(e.as,n.value.contentProps??{style:{position:"relative"}},[t.component?fi(t.component,{onVnodeMounted:Is,onVnodeUpdated:Is}):"404 Page Not Found"])}}),rf="/Coalesce/intellitect-text-white.svg",lf="/Coalesce/intellitect-text-black.svg",af=Oi({setup(e,{slots:t}){const n=ue(!1);return jt(()=>{n.value=!0}),()=>n.value&&t.default?t.default():null}});function cf(){Ce&&window.addEventListener("click",e=>{var n;const t=e.target;if(t.matches(".vp-code-group input")){const i=(n=t.parentElement)==null?void 0:n.parentElement;if(!i)return;const s=Array.from(i.querySelectorAll("input")).indexOf(t);if(s<0)return;const o=i.querySelector(".blocks");if(!o)return;const r=Array.from(o.children).find(f=>f.classList.contains("active"));if(!r)return;const l=o.children[s];if(!l||r===l)return;r.classList.remove("active"),l.classList.add("active");const a=i==null?void 0:i.querySelector(`label[for="${t.id}"]`);a==null||a.scrollIntoView({block:"nearest"})}})}function ff(){if(Ce){const e=new WeakMap;window.addEventListener("click",t=>{var i;const n=t.target;if(n.matches('div[class*="language-"] > button.copy')){const s=n.parentElement,o=(i=n.nextElementSibling)==null?void 0:i.nextElementSibling;if(!s||!o)return;const r=/language-(shellscript|shell|bash|sh|zsh)/.test(s.className);let l="";o.querySelectorAll("span.line:not(.diff.remove)").forEach(a=>l+=(a.textContent||"")+` +`),l=l.slice(0,-1),r&&(l=l.replace(/^ *(\$|>) /gm,"").trim()),vc(l).then(()=>{n.classList.add("copied"),clearTimeout(e.get(n));const a=setTimeout(()=>{n.classList.remove("copied"),n.blur(),e.delete(n)},2e3);e.set(n,a)})}})}}async function vc(e){try{return navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),n=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const i=document.getSelection(),s=i?i.rangeCount>0&&i.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),s&&(i.removeAllRanges(),i.addRange(s)),n&&n.focus()}}function uf(e,t){let n=[],i=!0;const s=o=>{if(i){i=!1;return}const r=o.map(Fs);n.forEach((l,a)=>{const f=r.findIndex(p=>p==null?void 0:p.isEqualNode(l??null));f!==-1?delete r[f]:(l==null||l.remove(),delete n[a])}),r.forEach(l=>l&&document.head.appendChild(l)),n=[...n,...r].filter(Boolean)};fo(()=>{const o=e.data,r=t.value,l=o&&o.description,a=o&&o.frontmatter.head||[],f=Qo(r,o);f!==document.title&&(document.title=f);const p=l||r.description;let d=document.querySelector("meta[name=description]");d?d.getAttribute("content")!==p&&d.setAttribute("content",p):Fs(["meta",{name:"description",content:p}]),s(Zo(r.head,bc(a)))})}function Fs([e,t,n]){const i=document.createElement(e);for(const s in t)i.setAttribute(s,t[s]);return n&&(i.innerHTML=n),e==="script"&&!t.async&&(i.async=!1),i}function yc(e){return e[0]==="meta"&&e[1]&&e[1].name==="description"}function bc(e){return e.filter(t=>!yc(t))}const Zn=new Set,tr=()=>document.createElement("link"),_c=e=>{const t=tr();t.rel="prefetch",t.href=e,document.head.appendChild(t)},wc=e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};let cn;const Cc=Ce&&(cn=tr())&&cn.relList&&cn.relList.supports&&cn.relList.supports("prefetch")?_c:wc;function pf(){if(!Ce||!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const i=()=>{n&&n.disconnect(),n=new IntersectionObserver(o=>{o.forEach(r=>{if(r.isIntersecting){const l=r.target;n.unobserve(l);const{pathname:a}=l;if(!Zn.has(a)){Zn.add(a);const f=mc(a);f&&Cc(f)}}})}),t(()=>{document.querySelectorAll("#app a").forEach(o=>{const{hostname:r,pathname:l}=new URL(o.href instanceof SVGAnimatedString?o.href.animVal:o.href,o.baseURI),a=l.match(/\.\w+$/);a&&a[0]!==".html"||o.target!=="_blank"&&r===location.hostname&&(l!==location.pathname?n.observe(o):Zn.add(l))})})};jt(i);const s=er();Ye(()=>s.path,i),Ln(()=>{n&&n.disconnect()})}export{Lc as $,Ln as A,Rc as B,vl as C,jc as D,Ic as E,ge as F,eo as G,nf as H,ie as I,Oc as J,Yo as K,er as L,Vl as M,bt as N,xi as O,On as P,ef as Q,Hc as R,jn as S,ko as T,Zc as U,zr as V,Mc as W,Qc as X,Gc as Y,Ml as Z,Wc as _,Io as a,Uc as a0,Bc as a1,Nc as a2,qc as a3,Vc as a4,zc as a5,Yc as a6,Jc as a7,Xc as a8,Sn as a9,Tc as aa,Pc as ab,Dc as ac,rf as ad,lf as ae,fi as af,uf as ag,hc as ah,tf as ai,cc as aj,of as ak,af as al,at as am,Kc as an,sf as ao,mc as ap,pf as aq,ff as ar,cf as as,xc as at,Ro as b,kc as c,Oi as d,$c as e,Os as f,ne as g,ue as h,ic as i,jt as j,Mo as k,pc as l,Ai as m,vi as n,jo as o,Ac as p,Sc as q,Fc as r,nc as s,Ec as t,fc as u,Ce as v,il as w,qa as x,Ye as y,fo as z}; diff --git a/assets/chunks/index.esm.VSePrB68.js b/assets/chunks/index.esm.VSePrB68.js new file mode 100644 index 000000000..11aa8c1b8 --- /dev/null +++ b/assets/chunks/index.esm.VSePrB68.js @@ -0,0 +1,7 @@ +var Ae=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},xe={exports:{}};(function(h,b){(function(j,n){h.exports=n()})(Ae,()=>{return j={770:function(S,k,_){var O=this&&this.__importDefault||function(u){return u&&u.__esModule?u:{default:u}};Object.defineProperty(k,"__esModule",{value:!0}),k.setDefaultDebugCall=k.createOnigScanner=k.createOnigString=k.loadWASM=k.OnigScanner=k.OnigString=void 0;const N=O(_(418));let s=null,m=!1;class L{static _utf8ByteLength(l){let p=0;for(let i=0,c=l.length;i=55296&&e<=56319&&i+1=56320&&y<=57343&&(t=65536+(e-55296<<10)|y-56320,r=!0)}p+=t<=127?1:t<=2047?2:t<=65535?3:4,r&&i++}return p}constructor(l){const p=l.length,i=L._utf8ByteLength(l),c=i!==p,e=c?new Uint32Array(p+1):null;c&&(e[p]=i);const t=c?new Uint32Array(i+1):null;c&&(t[i]=p);const r=new Uint8Array(i);let y=0;for(let v=0;v=55296&&T<=56319&&v+1=56320&&q<=57343&&(x=65536+(T-55296<<10)|q-56320,I=!0)}c&&(e[v]=y,I&&(e[v+1]=y),x<=127?t[y+0]=v:x<=2047?(t[y+0]=v,t[y+1]=v):x<=65535?(t[y+0]=v,t[y+1]=v,t[y+2]=v):(t[y+0]=v,t[y+1]=v,t[y+2]=v,t[y+3]=v)),x<=127?r[y++]=x:x<=2047?(r[y++]=192|(1984&x)>>>6,r[y++]=128|(63&x)>>>0):x<=65535?(r[y++]=224|(61440&x)>>>12,r[y++]=128|(4032&x)>>>6,r[y++]=128|(63&x)>>>0):(r[y++]=240|(1835008&x)>>>18,r[y++]=128|(258048&x)>>>12,r[y++]=128|(4032&x)>>>6,r[y++]=128|(63&x)>>>0),I&&v++}this.utf16Length=p,this.utf8Length=i,this.utf16Value=l,this.utf8Value=r,this.utf16OffsetToUtf8=e,this.utf8OffsetToUtf16=t}createString(l){const p=l._omalloc(this.utf8Length);return l.HEAPU8.set(this.utf8Value,p),p}}class w{constructor(l){if(this.id=++w.LAST_ID,!s)throw new Error("Must invoke loadWASM first.");this._onigBinding=s,this.content=l;const p=new L(l);this.utf16Length=p.utf16Length,this.utf8Length=p.utf8Length,this.utf16OffsetToUtf8=p.utf16OffsetToUtf8,this.utf8OffsetToUtf16=p.utf8OffsetToUtf16,this.utf8Length<1e4&&!w._sharedPtrInUse?(w._sharedPtr||(w._sharedPtr=s._omalloc(1e4)),w._sharedPtrInUse=!0,s.HEAPU8.set(p.utf8Value,w._sharedPtr),this.ptr=w._sharedPtr):this.ptr=p.createString(s)}convertUtf8OffsetToUtf16(l){return this.utf8OffsetToUtf16?l<0?0:l>this.utf8Length?this.utf16Length:this.utf8OffsetToUtf16[l]:l}convertUtf16OffsetToUtf8(l){return this.utf16OffsetToUtf8?l<0?0:l>this.utf16Length?this.utf8Length:this.utf16OffsetToUtf8[l]:l}dispose(){this.ptr===w._sharedPtr?w._sharedPtrInUse=!1:this._onigBinding._ofree(this.ptr)}}k.OnigString=w,w.LAST_ID=0,w._sharedPtr=0,w._sharedPtrInUse=!1;class o{constructor(l){if(!s)throw new Error("Must invoke loadWASM first.");const p=[],i=[];for(let r=0,y=l.length;rWebAssembly.instantiateStreaming(t,r)}(e):function(t){return async r=>{const y=await t.arrayBuffer();return WebAssembly.instantiate(y,r)}}(e):function(t){return r=>WebAssembly.instantiate(t,r)}(e)}return d=new Promise((e,t)=>{i=e,c=t}),function(e,t,r,y){(0,N.default)({print:t,instantiateWasm:(v,T)=>{if(typeof performance>"u"){const x=()=>Date.now();v.env.emscripten_get_now=x,v.wasi_snapshot_preview1.emscripten_get_now=x}return e(v).then(x=>T(x.instance),y),{}}}).then(v=>{s=v,r()})}(l,p,i,c),d},k.createOnigString=function(u){return new w(u)},k.createOnigScanner=function(u){return new o(u)},k.setDefaultDebugCall=function(u){m=u}},418:S=>{var k=(typeof document<"u"&&document.currentScript&&document.currentScript.src,function(_){var O,N,s=(_=_||{})!==void 0?_:{};s.ready=new Promise(function(E,$){O=E,N=$});var m,L=Object.assign({},s),w=!1,o="";function A(E){return s.locateFile?s.locateFile(E,o):o+E}m=function(E){let $;return typeof readbuffer=="function"?new Uint8Array(readbuffer(E)):($=read(E,"binary"),c(typeof $=="object"),$)},typeof scriptArgs<"u"&&scriptArgs,typeof onig_print<"u"&&(typeof console>"u"&&(console={}),console.log=onig_print,console.warn=console.error=typeof printErr<"u"?printErr:onig_print);var d,u,l=s.print||console.log.bind(console),p=s.printErr||console.warn.bind(console);Object.assign(s,L),L=null,s.arguments&&s.arguments,s.thisProgram&&s.thisProgram,s.quit&&s.quit,s.wasmBinary&&(d=s.wasmBinary),s.noExitRuntime,typeof WebAssembly!="object"&&B("no native wasm support detected");var i=!1;function c(E,$){E||B($)}var e,t,r,y=typeof TextDecoder<"u"?new TextDecoder("utf8"):void 0;function v(E,$,ne){for(var pe=$+ne,Q=$;E[Q]&&!(Q>=pe);)++Q;if(Q-$>16&&E.buffer&&y)return y.decode(E.subarray($,Q));for(var ee="";$>10,56320|1023&Se)}}else ee+=String.fromCharCode((31&ue)<<6|fe)}else ee+=String.fromCharCode(ue)}return ee}function T(E,$){return E?v(t,E,$):""}function x(E){e=E,s.HEAP8=new Int8Array(E),s.HEAP16=new Int16Array(E),s.HEAP32=new Int32Array(E),s.HEAPU8=t=new Uint8Array(E),s.HEAPU16=new Uint16Array(E),s.HEAPU32=r=new Uint32Array(E),s.HEAPF32=new Float32Array(E),s.HEAPF64=new Float64Array(E)}s.INITIAL_MEMORY;var I=[],q=[],K=[];function J(){if(s.preRun)for(typeof s.preRun=="function"&&(s.preRun=[s.preRun]);s.preRun.length;)f(s.preRun.shift());V(I)}function Z(){V(q)}function he(){if(s.postRun)for(typeof s.postRun=="function"&&(s.postRun=[s.postRun]);s.postRun.length;)g(s.postRun.shift());V(K)}function f(E){I.unshift(E)}function a(E){q.unshift(E)}function g(E){K.unshift(E)}var P=0,C=null;function M(E){P++,s.monitorRunDependencies&&s.monitorRunDependencies(P)}function W(E){if(P--,s.monitorRunDependencies&&s.monitorRunDependencies(P),P==0&&C){var $=C;C=null,$()}}function B(E){s.onAbort&&s.onAbort(E),p(E="Aborted("+E+")"),i=!0,E+=". Build with -sASSERTIONS for more info.";var $=new WebAssembly.RuntimeError(E);throw N($),$}var G,D,H="data:application/octet-stream;base64,";function U(E){return E.startsWith(H)}function R(E){try{if(E==G&&d)return new Uint8Array(d);if(m)return m(E);throw"both async and sync fetching of the wasm failed"}catch($){B($)}}function F(){return d||!w||typeof fetch!="function"?Promise.resolve().then(function(){return R(G)}):fetch(G,{credentials:"same-origin"}).then(function(E){if(!E.ok)throw"failed to load wasm binary file at '"+G+"'";return E.arrayBuffer()}).catch(function(){return R(G)})}function X(){var E={env:ce,wasi_snapshot_preview1:ce};function $(Q,ee){var ue=Q.exports;s.asm=ue,x((u=s.asm.memory).buffer),s.asm.__indirect_function_table,a(s.asm.__wasm_call_ctors),W()}function ne(Q){$(Q.instance)}function pe(Q){return F().then(function(ee){return WebAssembly.instantiate(ee,E)}).then(function(ee){return ee}).then(Q,function(ee){p("failed to asynchronously prepare wasm: "+ee),B(ee)})}if(M(),s.instantiateWasm)try{return s.instantiateWasm(E,$)}catch(Q){p("Module.instantiateWasm callback failed with error: "+Q),N(Q)}return(d||typeof WebAssembly.instantiateStreaming!="function"||U(G)||typeof fetch!="function"?pe(ne):fetch(G,{credentials:"same-origin"}).then(function(Q){return WebAssembly.instantiateStreaming(Q,E).then(ne,function(ee){return p("wasm streaming compile failed: "+ee),p("falling back to ArrayBuffer instantiation"),pe(ne)})})).catch(N),{}}function V(E){for(;E.length>0;)E.shift()(s)}function ae(E,$,ne){t.copyWithin(E,$,$+ne)}function oe(E){try{return u.grow(E-e.byteLength+65535>>>16),x(u.buffer),1}catch{}}function te(E){var $,ne=t.length,pe=2147483648;if((E>>>=0)>pe)return!1;for(var Q=1;Q<=4;Q*=2){var ee=ne*(1+.2/Q);if(ee=Math.min(ee,E+100663296),oe(Math.min(pe,($=Math.max(E,ee))+(65536-$%65536)%65536)))return!0}return!1}U(G="onig.wasm")||(G=A(G)),D=typeof dateNow<"u"?dateNow:()=>performance.now();var se=[null,[],[]];function Y(E,$){var ne=se[E];$===0||$===10?((E===1?l:p)(v(ne,0)),ne.length=0):ne.push($)}function ie(E,$,ne,pe){for(var Q=0,ee=0;ee>2],fe=r[$+4>>2];$+=8;for(var de=0;de>2]=Q,0}var z,ce={emscripten_get_now:D,emscripten_memcpy_big:ae,emscripten_resize_heap:te,fd_write:ie};function re(E){function $(){z||(z=!0,s.calledRun=!0,i||(Z(),O(s),s.onRuntimeInitialized&&s.onRuntimeInitialized(),he()))}P>0||(J(),P>0||(s.setStatus?(s.setStatus("Running..."),setTimeout(function(){setTimeout(function(){s.setStatus("")},1),$()},1)):$()))}if(X(),s.___wasm_call_ctors=function(){return(s.___wasm_call_ctors=s.asm.__wasm_call_ctors).apply(null,arguments)},s.___errno_location=function(){return(s.___errno_location=s.asm.__errno_location).apply(null,arguments)},s._omalloc=function(){return(s._omalloc=s.asm.omalloc).apply(null,arguments)},s._ofree=function(){return(s._ofree=s.asm.ofree).apply(null,arguments)},s._getLastOnigError=function(){return(s._getLastOnigError=s.asm.getLastOnigError).apply(null,arguments)},s._createOnigScanner=function(){return(s._createOnigScanner=s.asm.createOnigScanner).apply(null,arguments)},s._freeOnigScanner=function(){return(s._freeOnigScanner=s.asm.freeOnigScanner).apply(null,arguments)},s._findNextOnigScannerMatch=function(){return(s._findNextOnigScannerMatch=s.asm.findNextOnigScannerMatch).apply(null,arguments)},s._findNextOnigScannerMatchDbg=function(){return(s._findNextOnigScannerMatchDbg=s.asm.findNextOnigScannerMatchDbg).apply(null,arguments)},s.stackSave=function(){return(s.stackSave=s.asm.stackSave).apply(null,arguments)},s.stackRestore=function(){return(s.stackRestore=s.asm.stackRestore).apply(null,arguments)},s.stackAlloc=function(){return(s.stackAlloc=s.asm.stackAlloc).apply(null,arguments)},s.dynCall_jiji=function(){return(s.dynCall_jiji=s.asm.dynCall_jiji).apply(null,arguments)},s.UTF8ToString=T,C=function E(){z||re(),z||(C=E)},s.preInit)for(typeof s.preInit=="function"&&(s.preInit=[s.preInit]);s.preInit.length>0;)s.preInit.pop()();return re(),_.ready});S.exports=k}},n={},function S(k){var _=n[k];if(_!==void 0)return _.exports;var O=n[k]={exports:{}};return j[k].call(O.exports,O,O.exports,S),O.exports}(770);var j,n})})(xe);var _e=xe.exports,Re={exports:{}};(function(h,b){(function(j,n){h.exports=n()})(Ae,function(){return function(j){var n={};function S(k){if(n[k])return n[k].exports;var _=n[k]={i:k,l:!1,exports:{}};return j[k].call(_.exports,_,_.exports,S),_.l=!0,_.exports}return S.m=j,S.c=n,S.d=function(k,_,O){S.o(k,_)||Object.defineProperty(k,_,{enumerable:!0,get:O})},S.r=function(k){typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(k,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(k,"__esModule",{value:!0})},S.t=function(k,_){if(1&_&&(k=S(k)),8&_||4&_&&typeof k=="object"&&k&&k.__esModule)return k;var O=Object.create(null);if(S.r(O),Object.defineProperty(O,"default",{enumerable:!0,value:k}),2&_&&typeof k!="string")for(var N in k)S.d(O,N,(function(s){return k[s]}).bind(null,N));return O},S.n=function(k){var _=k&&k.__esModule?function(){return k.default}:function(){return k};return S.d(_,"a",_),_},S.o=function(k,_){return Object.prototype.hasOwnProperty.call(k,_)},S.p="",S(S.s=3)}([function(j,n,S){Object.defineProperty(n,"__esModule",{value:!0});var k=S(1),_=S(5),O=S(6),N=S(2),s=typeof performance>"u"?function(){return Date.now()}:function(){return performance.now()};n.createGrammar=function(f,a,g,P,C,M){return new e(f,a,g,P,C,M)};var m=function(f){this.scopeName=f};n.FullScopeDependency=m;var L=function(){function f(a,g){this.scopeName=a,this.include=g}return f.prototype.toKey=function(){return this.scopeName+"#"+this.include},f}();n.PartialScopeDependency=L;var w=function(){function f(){this.full=[],this.partial=[],this.visitedRule=new Set,this._seenFull=new Set,this._seenPartial=new Set}return f.prototype.add=function(a){a instanceof m?this._seenFull.has(a.scopeName)||(this._seenFull.add(a.scopeName),this.full.push(a)):this._seenPartial.has(a.toKey())||(this._seenPartial.add(a.toKey()),this.partial.push(a))},f}();function o(f,a,g,P,C){for(var M=0,W=P;M=0){var U=D.substring(0,H),R=D.substring(H+1);U===a.scopeName?A(f,a,a,R,G):U===g.scopeName?A(f,a,g,R,G):f.add(new L(U,D.substring(H+1)))}else f.add(new m(D))}}}}function A(f,a,g,P,C){C===void 0&&(C=g.repository),C&&C[P]&&o(f,a,g,[C[P]],C)}function d(f,a,g){if(g.patterns&&Array.isArray(g.patterns)&&o(f,a,g,g.patterns,g.repository),g.injections){var P=[];for(var C in g.injections)P.push(g.injections[C]);o(f,a,g,P,g.repository)}}function u(f,a){if(!f)return!1;if(f===a)return!0;var g=a.length;return f.length>g&&f.substr(0,g)===a&&f[g]==="."}function l(f,a){if(a.length>")}var D=Object.keys(this._embeddedLanguages).map(function(H){return f._escapeRegExpCharacters(H)});D.length===0?this._embeddedLanguagesRegex=null:(D.sort(),D.reverse(),this._embeddedLanguagesRegex=new RegExp("^(("+D.join(")|(")+"))($|\\.)",""))}return f.prototype.onDidChangeTheme=function(){this._cache=new Map,this._defaultMetaData=new i("",this._initialLanguage,0,[this._themeProvider.getDefaults()])},f.prototype.getDefaultMetadata=function(){return this._defaultMetaData},f._escapeRegExpCharacters=function(a){return a.replace(/[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g,"\\$&")},f.prototype.getMetadataForScope=function(a){if(a===null)return f._NULL_SCOPE_METADATA;var g=this._cache.get(a);return g||(g=this._doGetMetadataForScope(a),this._cache.set(a,g),g)},f.prototype._doGetMetadataForScope=function(a){var g=this._scopeToLanguage(a),P=this._toStandardTokenType(a),C=this._themeProvider.themeMatch(a);return new i(a,g,P,C)},f.prototype._scopeToLanguage=function(a){if(!a||!this._embeddedLanguagesRegex)return 0;var g=a.match(this._embeddedLanguagesRegex);if(!g)return 0;var P=this._embeddedLanguages[g[1]]||0;return P||0},f.prototype._toStandardTokenType=function(a){var g=a.match(f.STANDARD_TOKEN_TYPE_REGEXP);if(!g)return 0;switch(g[1]){case"comment":return 1;case"string":return 2;case"regex":return 4;case"meta.embedded":return 8}throw new Error("Unexpected match for standard token type!")},f._NULL_SCOPE_METADATA=new i("",0,0,null),f.STANDARD_TOKEN_TYPE_REGEXP=/\b(comment|string|regex|meta\.embedded)\b/,f}(),e=function(){function f(a,g,P,C,M,W){if(this._scopeMetadataProvider=new c(g,M,P),this._onigLib=W,this._rootId=-1,this._lastRuleId=0,this._ruleId2desc=[null],this._includedGrammars={},this._grammarRepository=M,this._grammar=r(a,null),this._injections=null,this._tokenTypeMatchers=[],C)for(var B=0,G=Object.keys(C);BH)break;for(;D.length>0&&D[D.length-1].endPos<=F.start;)C.produceFromScopes(D[D.length-1].scopes,D[D.length-1].endPos),D.pop();if(D.length>0?C.produceFromScopes(D[D.length-1].scopes,F.start):C.produce(P,F.start),R.retokenizeCapturedWithRuleId){var X=R.getName(B,W),V=P.contentNameScopesList.push(f,X),ae=R.getContentName(B,W),oe=V.push(f,ae),te=P.push(R.retokenizeCapturedWithRuleId,F.start,-1,!1,null,V,oe),se=f.createOnigString(B.substring(0,F.end));x(f,se,g&&F.start===0,F.start,te,C,!1),t(se)}else{var Y=R.getName(B,W);if(Y!==null){var ie=(D.length>0?D[D.length-1].scopes:P.contentNameScopesList).push(f,Y);D.push(new J(ie,F.end))}}}}}for(;D.length>0;)C.produceFromScopes(D[D.length-1].scopes,D[D.length-1].endPos),D.pop()}}function v(f){for(var a=[],g=0,P=f.rules.length;g5&&console.warn("Rule "+oe.debugName+" ("+oe.id+") matching took "+ie+" against '"+R+"'"),Y&&console.log("matched rule id: "+te.rules[Y.index]+" from "+Y.captureIndices[0].start+" to "+Y.captureIndices[0].end)}return Y?{captureIndices:Y.captureIndices,matchedRuleId:te.rules[Y.index]}:null}(f,a,g,P,C,M),B=f.getInjections();if(B.length===0)return W;var G=function(U,R,F,X,V,ae,oe){for(var te,se=Number.MAX_VALUE,Y=null,ie=0,z=ae.contentNameScopesList.generateScopes(),ce=0,re=U.length;ce=se)&&(se=pe,Y=ne.captureIndices,te=$.rules[ne.index],ie=E.priority,se===V))break}}}return Y?{priorityMatch:ie===-1,captureIndices:Y,matchedRuleId:te}:null}(B,f,a,g,P,C,M);if(!G)return W;if(!W)return G;var D=W.captureIndices[0].start,H=G.captureIndices[0].start;return HV&&(V=re.captureIndices[0].end,X=!1))}return{stack:ae,linePos:V,anchorPosition:te,isFirstLine:X}}(f,a,g,P,C,M);C=H.stack,P=H.linePos,g=H.isFirstLine,D=H.anchorPosition}for(;!G;)U();function U(){N.DebugFlags.InDebugMode&&(console.log(""),console.log("@@scanNext "+P+": |"+a.content.substr(P).replace(/\n$/,"\\n")+"|"));var R=T(f,a,g,P,C,D);if(!R)return N.DebugFlags.InDebugMode&&console.log(" no more matches."),M.produce(C,B),void(G=!0);var F=R.captureIndices,X=R.matchedRuleId,V=!!(F&&F.length>0)&&F[0].end>P;if(X===-1){var ae=C.getRule(f);N.DebugFlags.InDebugMode&&console.log(" popping "+ae.debugName+" - "+ae.debugEndRegExp),M.produce(C,F[0].start),C=C.setContentNameScopesList(C.nameScopesList),y(f,a,g,C,M,ae.endCaptures,F),M.produce(C,F[0].end);var oe=C;if(C=C.pop(),D=oe.getAnchorPos(),!V&&oe.getEnterPos()===P)return N.DebugFlags.InDebugMode&&console.error("[1] - Grammar is in an endless loop - Grammar pushed & popped a rule without advancing"),C=oe,M.produce(C,B),void(G=!0)}else{var te=f.getRule(X);M.produce(C,F[0].start);var se=C,Y=te.getName(a.content,F),ie=C.contentNameScopesList.push(f,Y);if(C=C.push(X,P,D,F[0].end===B,null,ie,ie),te instanceof _.BeginEndRule){var z=te;N.DebugFlags.InDebugMode&&console.log(" pushing "+z.debugName+" - "+z.debugBeginRegExp),y(f,a,g,C,M,z.beginCaptures,F),M.produce(C,F[0].end),D=F[0].end;var ce=z.getContentName(a.content,F),re=ie.push(f,ce);if(C=C.setContentNameScopesList(re),z.endHasBackReferences&&(C=C.setEndRule(z.getEndWithResolvedBackReferences(a.content,F))),!V&&se.hasSameRuleAs(C))return N.DebugFlags.InDebugMode&&console.error("[2] - Grammar is in an endless loop - Grammar pushed the same rule without advancing"),C=C.pop(),M.produce(C,B),void(G=!0)}else if(te instanceof _.BeginWhileRule){if(z=te,N.DebugFlags.InDebugMode&&console.log(" pushing "+z.debugName),y(f,a,g,C,M,z.beginCaptures,F),M.produce(C,F[0].end),D=F[0].end,ce=z.getContentName(a.content,F),re=ie.push(f,ce),C=C.setContentNameScopesList(re),z.whileHasBackReferences&&(C=C.setEndRule(z.getWhileWithResolvedBackReferences(a.content,F))),!V&&se.hasSameRuleAs(C))return N.DebugFlags.InDebugMode&&console.error("[3] - Grammar is in an endless loop - Grammar pushed the same rule without advancing"),C=C.pop(),M.produce(C,B),void(G=!0)}else{var E=te;if(N.DebugFlags.InDebugMode&&console.log(" matched "+E.debugName+" - "+E.debugMatchRegExp),y(f,a,g,C,M,E.captures,F),M.produce(C,F[0].end),C=C.pop(),!V)return N.DebugFlags.InDebugMode&&console.error("[4] - Grammar is in an endless loop - Grammar is not advancing, nor is it pushing/popping"),C=C.safePop(),M.produce(C,B),void(G=!0)}}F[0].end>P&&(P=F[0].end,g=!1)}return C}n.Grammar=e;var I=function(){function f(){}return f.toBinaryStr=function(a){for(var g=a.toString(2);g.length<32;)g="0"+g;return g},f.printMetadata=function(a){var g=f.getLanguageId(a),P=f.getTokenType(a),C=f.getFontStyle(a),M=f.getForeground(a),W=f.getBackground(a);console.log({languageId:g,tokenType:P,fontStyle:C,foreground:M,background:W})},f.getLanguageId=function(a){return(255&a)>>>0},f.getTokenType=function(a){return(1792&a)>>>8},f.getFontStyle=function(a){return(14336&a)>>>11},f.getForeground=function(a){return(8372224&a)>>>14},f.getBackground=function(a){return(4286578688&a)>>>23},f.set=function(a,g,P,C,M,W){var B=f.getLanguageId(a),G=f.getTokenType(a),D=f.getFontStyle(a),H=f.getForeground(a),U=f.getBackground(a);return g!==0&&(B=g),P!==0&&(G=P===8?0:P),C!==-1&&(D=C),M!==0&&(H=M),W!==0&&(U=W),(B<<0|G<<8|D<<11|H<<14|U<<23)>>>0},f}();n.StackElementMetadata=I;var q=function(){function f(a,g,P){this.parent=a,this.scope=g,this.metadata=P}return f._equals=function(a,g){for(;;){if(a===g||!a&&!g)return!0;if(!a||!g||a.scope!==g.scope||a.metadata!==g.metadata)return!1;a=a.parent,g=g.parent}},f.prototype.equals=function(a){return f._equals(this,a)},f._matchesScope=function(a,g,P){return g===a||a.substring(0,P.length)===P},f._matches=function(a,g){if(g===null)return!0;for(var P=g.length,C=0,M=g[C],W=M+".";a;){if(this._matchesScope(a.scope,M,W)){if(++C===P)return!0;W=(M=g[C])+"."}a=a.parent}return!1},f.mergeMetadata=function(a,g,P){if(P===null)return a;var C=-1,M=0,W=0;if(P.themeData!==null)for(var B=0,G=P.themeData.length;B=0?f._push(this,a,g.split(/ /g)):f._push(this,a,[g])},f._generateScopes=function(a){for(var g=[],P=0;a;)g[P++]=a.scope,a=a.parent;return g.reverse(),g},f.prototype.generateScopes=function(){return f._generateScopes(this)},f}();n.ScopeListElement=q;var K=function(){function f(a,g,P,C,M,W,B,G){this.parent=a,this.depth=this.parent?this.parent.depth+1:1,this.ruleId=g,this._enterPos=P,this._anchorPos=C,this.beginRuleCapturedEOL=M,this.endRule=W,this.nameScopesList=B,this.contentNameScopesList=G}return f._structuralEquals=function(a,g){for(;;){if(a===g||!a&&!g)return!0;if(!a||!g||a.depth!==g.depth||a.ruleId!==g.ruleId||a.endRule!==g.endRule)return!1;a=a.parent,g=g.parent}},f._equals=function(a,g){return a===g||!!this._structuralEquals(a,g)&&a.contentNameScopesList.equals(g.contentNameScopesList)},f.prototype.clone=function(){return this},f.prototype.equals=function(a){return a!==null&&f._equals(this,a)},f._reset=function(a){for(;a;)a._enterPos=-1,a._anchorPos=-1,a=a.parent},f.prototype.reset=function(){f._reset(this)},f.prototype.pop=function(){return this.parent},f.prototype.safePop=function(){return this.parent?this.parent:this},f.prototype.push=function(a,g,P,C,M,W,B){return new f(this,a,g,P,C,M,W,B)},f.prototype.getEnterPos=function(){return this._enterPos},f.prototype.getAnchorPos=function(){return this._anchorPos},f.prototype.getRule=function(a){return a.getRule(this.ruleId)},f.prototype._writeString=function(a,g){return this.parent&&(g=this.parent._writeString(a,g)),a[g++]="("+this.ruleId+", TODO-"+this.nameScopesList+", TODO-"+this.contentNameScopesList+")",g},f.prototype.toString=function(){var a=[];return this._writeString(a,0),"["+a.join(",")+"]"},f.prototype.setContentNameScopesList=function(a){return this.contentNameScopesList===a?this:this.parent.push(this.ruleId,this._enterPos,this._anchorPos,this.beginRuleCapturedEOL,this.endRule,this.nameScopesList,a)},f.prototype.setEndRule=function(a){return this.endRule===a?this:new f(this.parent,this.ruleId,this._enterPos,this._anchorPos,this.beginRuleCapturedEOL,a,this.nameScopesList,this.contentNameScopesList)},f.prototype.hasSameRuleAs=function(a){return this.ruleId===a.ruleId},f.NULL=new f(null,0,0,0,!1,null,null,null),f}();n.StackElement=K;var J=function(f,a){this.scopes=f,this.endPos=a};n.LocalStackElement=J;var Z=function(){function f(a,g,P){this._emitBinaryTokens=a,this._tokenTypeOverrides=P,N.DebugFlags.InDebugMode?this._lineText=g:this._lineText=null,this._tokens=[],this._binaryTokens=[],this._lastTokenEndIndex=0}return f.prototype.produce=function(a,g){this.produceFromScopes(a.contentNameScopesList,g)},f.prototype.produceFromScopes=function(a,g){if(!(this._lastTokenEndIndex>=g)){if(this._emitBinaryTokens){for(var P=a.metadata,C=0,M=this._tokenTypeOverrides;C0&&this._binaryTokens[this._binaryTokens.length-1]===P||(this._binaryTokens.push(this._lastTokenEndIndex),this._binaryTokens.push(P)),void(this._lastTokenEndIndex=g)}var B=a.generateScopes();if(N.DebugFlags.InDebugMode){console.log(" token: |"+this._lineText.substring(this._lastTokenEndIndex,g).replace(/\n$/,"\\n")+"|");for(var G=0;G0&&this._tokens[this._tokens.length-1].startIndex===g-1&&this._tokens.pop(),this._tokens.length===0&&(this._lastTokenEndIndex=-1,this.produce(a,g),this._tokens[this._tokens.length-1].startIndex=0),this._tokens},f.prototype.getBinaryResult=function(a,g){this._binaryTokens.length>0&&this._binaryTokens[this._binaryTokens.length-2]===g-1&&(this._binaryTokens.pop(),this._binaryTokens.pop()),this._binaryTokens.length===0&&(this._lastTokenEndIndex=-1,this.produce(a,g),this._binaryTokens[this._binaryTokens.length-2]=0);for(var P=new Uint32Array(this._binaryTokens.length),C=0,M=this._binaryTokens.length;C0&&u[u.length-1])||t[0]!==6&&t[0]!==2)){p=0;continue}if(t[0]===3&&(!u||t[1]>u[0]&&t[1]")},w.prototype._loadGrammar=function(o,A,d,u){return k(this,void 0,void 0,function(){var l,p,i,c,e,t,r,y,v,T,x,I,q=this;return _(this,function(K){switch(K.label){case 0:l=new Set,p=new Set,l.add(o),i=[new m.FullScopeDependency(o)],K.label=1;case 1:return i.length>0?(c=i,i=[],[4,Promise.all(c.map(function(J){return q._loadSingleGrammar(J.scopeName)}))]):[3,3];case 2:for(K.sent(),e=new m.ScopeDependencyCollector,t=0,r=c;t0&&o[o.length-1])||i[0]!==6&&i[0]!==2)){d=0;continue}if(i[0]===3&&(!o||i[1]>o[0]&&i[1]v&&(v=I);for(var x=0;x<=v;x++)y[x]=null;for(var T in e)if(T!=="$vscodeTextmateLocation"){var I=parseInt(T,10),q=0;e[T].patterns&&(q=c.getCompiledRuleId(e[T],t,r)),y[I]=c.createCaptureRule(t,e[T].$vscodeTextmateLocation,e[T].name,e[T].contentName,q)}}return y},c._compilePatterns=function(e,t,r){var y=[];if(e)for(var v=0,T=e.length;v=0?(K=x.include.substring(0,Z),J=x.include.substring(Z+1)):K=x.include;var he=t.getExternalGrammar(K,r);if(he)if(J){var f=he.repository[J];f&&(I=c.getCompiledRuleId(f,t,he.repository))}else I=c.getCompiledRuleId(he.repository.$self,t,he.repository)}else I=c.getCompiledRuleId(x,t,r);if(I!==-1){var a=t.getRule(I),g=!1;if((a instanceof u||a instanceof l||a instanceof p)&&a.hasMissingPatterns&&a.patterns.length===0&&(g=!0),g)continue;y.push(I)}}return{patterns:y,hasMissingPatterns:(e?e.length:0)!==y.length}},c}();n.RuleFactory=i},function(j,n,S){function k(_){return!!_&&!!_.match(/[\w\.:]+/)}Object.defineProperty(n,"__esModule",{value:!0}),n.createMatchers=function(_,O){for(var N,s,m,L=[],w=(m=(s=/([LR]:|[\w\.:][\w\.:\-]*|[\,\|\-\(\)])/g).exec(N=_),{next:function(){if(!m)return null;var p=m[0];return m=s.exec(N),p}}),o=w.next();o!==null;){var A=0;if(o.length===2&&o.charAt(1)===":"){switch(o.charAt(0)){case"R":A=1;break;case"L":A=-1;break;default:console.log("Unknown priority "+o+" in scope selector")}o=w.next()}var d=l();if(L.push({matcher:d,priority:A}),o!==",")break;o=w.next()}return L;function u(){if(o==="-"){o=w.next();var p=u();return function(e){return!!p&&!p(e)}}if(o==="("){o=w.next();var i=function(){for(var e=[],t=l();t&&(e.push(t),o==="|"||o===",");){do o=w.next();while(o==="|"||o===",");t=l()}return function(r){return e.some(function(y){return y(r)})}}();return o===")"&&(o=w.next()),i}if(k(o)){var c=[];do c.push(o),o=w.next();while(k(o));return function(e){return O(c,e)}}return null}function l(){for(var p=[],i=u();i;)p.push(i),i=u();return function(c){return p.every(function(e){return e(c)})}}}},function(j,n){var S,k,_=j.exports={};function O(){throw new Error("setTimeout has not been defined")}function N(){throw new Error("clearTimeout has not been defined")}function s(p){if(S===setTimeout)return setTimeout(p,0);if((S===O||!S)&&setTimeout)return S=setTimeout,setTimeout(p,0);try{return S(p,0)}catch{try{return S.call(null,p,0)}catch{return S.call(this,p,0)}}}(function(){try{S=typeof setTimeout=="function"?setTimeout:O}catch{S=O}try{k=typeof clearTimeout=="function"?clearTimeout:N}catch{k=N}})();var m,L=[],w=!1,o=-1;function A(){w&&m&&(w=!1,m.length?L=m.concat(L):o=-1,L.length&&d())}function d(){if(!w){var p=s(A);w=!0;for(var i=L.length;i;){for(m=L,L=[];++o1)for(var c=1;c0;)_.charCodeAt(m)===10?(m++,L++,w=0):(m++,w++),R--}function A(R){N===null?m=R:o(R-m)}function d(){for(;m0&&_.charCodeAt(0)===65279&&(m=1);var i=0,c=null,e=[],t=[],r=null;function y(R,F){e.push(i),t.push(c),i=R,c=F}function v(){if(e.length===0)return T("illegal state stack");i=e.pop(),c=t.pop()}function T(R){throw new Error("Near offset "+m+": "+R+" ~~~"+_.substr(m,50)+"~~~")}var x,I,q,K=function(){if(r===null)return T("missing ");var R={};N!==null&&(R[N]={filename:O,line:L,char:w}),c[r]=R,r=null,y(1,R)},J=function(){if(r===null)return T("missing ");var R=[];c[r]=R,r=null,y(2,R)},Z=function(){var R={};N!==null&&(R[N]={filename:O,line:L,char:w}),c.push(R),y(1,R)},he=function(){var R=[];c.push(R),y(2,R)};function f(){if(i!==1)return T("unexpected ");v()}function a(){return i===1||i!==2?T("unexpected "):void v()}function g(R){if(i===1){if(r===null)return T("missing ");c[r]=R,r=null}else i===2?c.push(R):c=R}function P(R){if(isNaN(R))return T("cannot parse float");if(i===1){if(r===null)return T("missing ");c[r]=R,r=null}else i===2?c.push(R):c=R}function C(R){if(isNaN(R))return T("cannot parse integer");if(i===1){if(r===null)return T("missing ");c[r]=R,r=null}else i===2?c.push(R):c=R}function M(R){if(i===1){if(r===null)return T("missing ");c[r]=R,r=null}else i===2?c.push(R):c=R}function W(R){if(i===1){if(r===null)return T("missing ");c[r]=R,r=null}else i===2?c.push(R):c=R}function B(R){if(i===1){if(r===null)return T("missing ");c[r]=R,r=null}else i===2?c.push(R):c=R}function G(R){if(R.isClosed)return"";var F=p(""),F.replace(/&#([0-9]+);/g,function(X,V){return String.fromCodePoint(parseInt(V,10))}).replace(/&#x([0-9a-f]+);/g,function(X,V){return String.fromCodePoint(parseInt(V,16))}).replace(/&|<|>|"|'/g,function(X){switch(X){case"&":return"&";case"<":return"<";case">":return">";case""":return'"';case"'":return"'"}return X})}for(;m=s));){var D=_.charCodeAt(m);if(o(1),D!==60)return T("expected <");if(m>=s)return T("unexpected end of input");var H=_.charCodeAt(m);if(H!==63)if(H!==33){if(H===47){if(o(1),d(),u("plist")){l(">");continue}if(u("dict")){l(">"),f();continue}if(u("array")){l(">"),a();continue}return T("unexpected closed tag")}var U=(I=void 0,q=void 0,I=p(">"),q=!1,I.charCodeAt(I.length-1)===47&&(q=!0,I=I.substring(0,I.length-1)),{name:I.trim(),isClosed:q});switch(U.name){case"dict":i===1?K():i===2?Z():(c={},N!==null&&(c[N]={filename:O,line:L,char:w}),y(1,c)),U.isClosed&&f();continue;case"array":i===1?J():i===2?he():y(2,c=[]),U.isClosed&&a();continue;case"key":x=G(U),i!==1?T("unexpected "):r!==null?T("too many "):r=x;continue;case"string":g(G(U));continue;case"real":P(parseFloat(G(U)));continue;case"integer":C(parseInt(G(U),10));continue;case"date":M(new Date(G(U)));continue;case"data":W(G(U));continue;case"true":G(U),B(!0);continue;case"false":G(U),B(!1);continue}if(!/^plist/.test(U.name))return T("unexpected opened tag "+U.name)}else{if(o(1),u("--")){l("-->");continue}l(">")}else o(1),l("?>")}return c}Object.defineProperty(n,"__esModule",{value:!0}),n.parseWithLocation=function(_,O,N){return k(_,O,N)},n.parse=function(_){return k(_,null,null)}},function(j,n,S){function k(s,m){throw new Error("Near offset "+s.pos+": "+m+" ~~~"+s.source.substr(s.pos,50)+"~~~")}Object.defineProperty(n,"__esModule",{value:!0}),n.parse=function(s,m,L){var w=new _(s),o=new O,A=0,d=null,u=[],l=[];function p(){u.push(A),l.push(d)}function i(){A=u.pop(),d=l.pop()}function c(y){k(w,y)}for(;N(w,o);){if(A===0){if(d!==null&&c("too many constructs in root"),o.type===3){d={},L&&(d.$vscodeTextmateLocation=o.toLocation(m)),p(),A=1;continue}if(o.type===2){d=[],p(),A=4;continue}c("unexpected token in root")}if(A===2){if(o.type===5){i();continue}if(o.type===7){A=3;continue}c("expected , or }")}if(A===1||A===3){if(A===1&&o.type===5){i();continue}if(o.type===1){var e=o.value;if(N(w,o)&&o.type===6||c("expected colon"),N(w,o)||c("expected value"),A=2,o.type===1){d[e]=o.value;continue}if(o.type===8){d[e]=null;continue}if(o.type===9){d[e]=!0;continue}if(o.type===10){d[e]=!1;continue}if(o.type===11){d[e]=parseFloat(o.value);continue}if(o.type===2){var t=[];d[e]=t,p(),A=4,d=t;continue}if(o.type===3){var r={};L&&(r.$vscodeTextmateLocation=o.toLocation(m)),d[e]=r,p(),A=1,d=r;continue}}c("unexpected token in dict")}if(A===5){if(o.type===4){i();continue}if(o.type===7){A=6;continue}c("expected , or ]")}if(A===4||A===6){if(A===4&&o.type===4){i();continue}if(A=5,o.type===1){d.push(o.value);continue}if(o.type===8){d.push(null);continue}if(o.type===9){d.push(!0);continue}if(o.type===10){d.push(!1);continue}if(o.type===11){d.push(parseFloat(o.value));continue}if(o.type===2){t=[],d.push(t),p(),A=4,d=t;continue}if(o.type===3){r={},L&&(r.$vscodeTextmateLocation=o.toLocation(m)),d.push(r),p(),A=1,d=r;continue}c("unexpected token in array")}c("unknown state")}return l.length!==0&&c("unclosed constructs"),d};var _=function(s){this.source=s,this.pos=0,this.len=s.length,this.line=1,this.char=0},O=function(){function s(){this.value=null,this.type=0,this.offset=-1,this.len=-1,this.line=-1,this.char=-1}return s.prototype.toLocation=function(m){return{filename:m,line:this.line,char:this.char}},s}();function N(s,m){m.value=null,m.type=0,m.offset=-1,m.len=-1,m.line=-1,m.char=-1;for(var L,w=s.source,o=s.pos,A=s.len,d=s.line,u=s.char;;){if(o>=A)return!1;if((L=w.charCodeAt(o))!==32&&L!==9&&L!==13){if(L!==10)break;o++,d++,u=0}else o++,u++}if(m.offset=o,m.line=d,m.char=u,L===34){for(m.type=1,o++,u++;;){if(o>=A)return!1;if(L=w.charCodeAt(o),o++,u++,L!==92){if(L===34)break}else o++,u++}m.value=w.substring(m.offset+1,o-1).replace(/\\u([0-9A-Fa-f]{4})/g,function(l,p){return String.fromCodePoint(parseInt(p,16))}).replace(/\\(.)/g,function(l,p){switch(p){case'"':return'"';case"\\":return"\\";case"/":return"/";case"b":return"\b";case"f":return"\f";case"n":return` +`;case"r":return"\r";case"t":return" ";default:k(s,"invalid escape sequence")}throw new Error("unreachable")})}else if(L===91)m.type=2,o++,u++;else if(L===123)m.type=3,o++,u++;else if(L===93)m.type=4,o++,u++;else if(L===125)m.type=5,o++,u++;else if(L===58)m.type=6,o++,u++;else if(L===44)m.type=7,o++,u++;else if(L===110){if(m.type=8,o++,u++,(L=w.charCodeAt(o))!==117||(o++,u++,(L=w.charCodeAt(o))!==108)||(o++,u++,(L=w.charCodeAt(o))!==108))return!1;o++,u++}else if(L===116){if(m.type=9,o++,u++,(L=w.charCodeAt(o))!==114||(o++,u++,(L=w.charCodeAt(o))!==117)||(o++,u++,(L=w.charCodeAt(o))!==101))return!1;o++,u++}else if(L===102){if(m.type=10,o++,u++,(L=w.charCodeAt(o))!==97||(o++,u++,(L=w.charCodeAt(o))!==108)||(o++,u++,(L=w.charCodeAt(o))!==115)||(o++,u++,(L=w.charCodeAt(o))!==101))return!1;o++,u++}else for(m.type=11;;){if(o>=A)return!1;if(!((L=w.charCodeAt(o))===46||L>=48&&L<=57||L===101||L===69||L===45||L===43))break;o++,u++}return m.len=o-m.offset,m.value===null&&(m.value=w.substr(m.offset,m.len)),s.pos=o,s.line=d,s.char=u,!0}},function(j,n,S){Object.defineProperty(n,"__esModule",{value:!0});var k=function(d,u,l,p,i,c){this.scope=d,this.parentScopes=u,this.index=l,this.fontStyle=p,this.foreground=i,this.background=c};function _(d){return!!/^#[0-9a-f]{6}$/i.test(d)||!!/^#[0-9a-f]{8}$/i.test(d)||!!/^#[0-9a-f]{3}$/i.test(d)||!!/^#[0-9a-f]{4}$/i.test(d)}function O(d){if(!d)return[];if(!d.settings||!Array.isArray(d.settings))return[];for(var u=d.settings,l=[],p=0,i=0,c=u.length;i1&&(K=I.slice(0,I.length-1)).reverse(),l[p++]=new k(q,K,i,r,T,x)}}}return l}function N(d,u){d.sort(function(x,I){var q=L(x.scope,I.scope);return q!==0||(q=w(x.parentScopes,I.parentScopes))!==0?q:x.index-I.index});for(var l=0,p="#000000",i="#ffffff";d.length>=1&&d[0].scope==="";){var c=d.shift();c.fontStyle!==-1&&(l=c.fontStyle),c.foreground!==null&&(p=c.foreground),c.background!==null&&(i=c.background)}for(var e=new s(u),t=new o(0,null,l,e.getId(p),e.getId(i)),r=new A(new o(0,null,-1,0,0),[]),y=0,v=d.length;yu?1:0}function w(d,u){if(d===null&&u===null)return 0;if(!d)return-1;if(!u)return 1;var l=d.length,p=u.length;if(l===p){for(var i=0;iu?console.log("how did this happen?"):this.scopeDepth=u,l!==-1&&(this.fontStyle=l),p!==0&&(this.foreground=p),i!==0&&(this.background=i)},d}();n.ThemeTrieElementRule=o;var A=function(){function d(u,l,p){l===void 0&&(l=[]),p===void 0&&(p={}),this._mainRule=u,this._rulesWithParentScopes=l,this._children=p}return d._sortBySpecificity=function(u){return u.length===1||u.sort(this._cmpBySpecificity),u},d._cmpBySpecificity=function(u,l){if(u.scopeDepth===l.scopeDepth){var p=u.parentScopes,i=l.parentScopes,c=p===null?0:p.length,e=i===null?0:i.length;if(c===e)for(var t=0;t>>0}static getTokenType(b){return(b&1792)>>>8}static getFontStyle(b){return(b&14336)>>>11}static getForeground(b){return(b&8372224)>>>14}static getBackground(b){return(b&4286578688)>>>23}static set(b,j,n,S,k,_){let O=le.getLanguageId(b),N=le.getTokenType(b),s=le.getFontStyle(b),m=le.getForeground(b),L=le.getBackground(b);return j!==0&&(O=j),n!==0&&(N=n===8?0:n),S!==me.NotSet&&(s=S),k!==0&&(m=k),_!==0&&(L=_),(O<<0|N<<8|s<<11|m<<14|L<<23)>>>0}}function Be(h){return h.endsWith("/")||h.endsWith("\\")?h.slice(0,-1):h}function We(h){return h.startsWith("./")?h.slice(2):h}function Fe(h){const b=h.split(/[\/\\]/g);return b[b.length-2]}function $e(...h){return h.map(Be).map(We).join("/")}function Ue(h,b){const j=new Map;for(const n of h){const S=b(n);j.has(S)?j.get(S).push(n):j.set(S,[n])}return j}function qe(h,b){b===void 0&&(b=!1);var j=h.length,n=0,S="",k=0,_=16,O=0,N=0,s=0,m=0,L=0;function w(i,c){for(var e=0,t=0;e=48&&r<=57)t=t*16+r-48;else if(r>=65&&r<=70)t=t*16+r-65+10;else if(r>=97&&r<=102)t=t*16+r-97+10;else break;n++,e++}return e=j){i+=h.substring(c,n),L=2;break}var e=h.charCodeAt(n);if(e===34){i+=h.substring(c,n),n++;break}if(e===92){if(i+=h.substring(c,n),n++,n>=j){L=2;break}var t=h.charCodeAt(n++);switch(t){case 34:i+='"';break;case 92:i+="\\";break;case 47:i+="/";break;case 98:i+="\b";break;case 102:i+="\f";break;case 110:i+=` +`;break;case 114:i+="\r";break;case 116:i+=" ";break;case 117:var r=w(4,!0);r>=0?i+=String.fromCharCode(r):L=4;break;default:L=5}c=n;continue}if(e>=0&&e<=31)if(ye(e)){i+=h.substring(c,n),L=2;break}else L=6;n++}return i}function u(){if(S="",L=0,k=n,N=O,m=s,n>=j)return k=j,_=17;var i=h.charCodeAt(n);if(be(i)){do n++,S+=String.fromCharCode(i),i=h.charCodeAt(n);while(be(i));return _=15}if(ye(i))return n++,S+=String.fromCharCode(i),i===13&&h.charCodeAt(n)===10&&(n++,S+=` +`),O++,s=n,_=14;switch(i){case 123:return n++,_=1;case 125:return n++,_=2;case 91:return n++,_=3;case 93:return n++,_=4;case 58:return n++,_=6;case 44:return n++,_=5;case 34:return n++,S=d(),_=10;case 47:var c=n-1;if(h.charCodeAt(n+1)===47){for(n+=2;n=12&&i<=15);return i}return{setPosition:o,getPosition:function(){return n},scan:b?p:u,getToken:function(){return _},getTokenValue:function(){return S},getTokenOffset:function(){return k},getTokenLength:function(){return n-k},getTokenStartLine:function(){return N},getTokenStartCharacter:function(){return k-m},getTokenError:function(){return L}}}function be(h){return h===32||h===9||h===11||h===12||h===160||h===5760||h>=8192&&h<=8203||h===8239||h===8287||h===12288||h===65279}function ye(h){return h===10||h===13||h===8232||h===8233}function ge(h){return h>=48&&h<=57}var ve;(function(h){h.DEFAULT={allowTrailingComma:!1}})(ve||(ve={}));function ze(h,b,j){b===void 0&&(b=[]),j===void 0&&(j=ve.DEFAULT);var n=null,S=[],k=[];function _(N){Array.isArray(S)?S.push(N):n!==null&&(S[n]=N)}var O={onObjectBegin:function(){var N={};_(N),k.push(S),S=N,n=null},onObjectProperty:function(N){n=N},onObjectEnd:function(){S=k.pop()},onArrayBegin:function(){var N=[];_(N),k.push(S),S=N,n=null},onArrayEnd:function(){S=k.pop()},onLiteralValue:_,onError:function(N,s,m){b.push({error:N,offset:s,length:m})}};return He(h,O,j),S[0]}function He(h,b,j){j===void 0&&(j=ve.DEFAULT);var n=qe(h,!1);function S(v){return v?function(){return v(n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter())}:function(){return!0}}function k(v){return v?function(T){return v(T,n.getTokenOffset(),n.getTokenLength(),n.getTokenStartLine(),n.getTokenStartCharacter())}:function(){return!0}}var _=S(b.onObjectBegin),O=k(b.onObjectProperty),N=S(b.onObjectEnd),s=S(b.onArrayBegin),m=S(b.onArrayEnd),L=k(b.onLiteralValue),w=k(b.onSeparator),o=S(b.onComment),A=k(b.onError),d=j&&j.disallowComments,u=j&&j.allowTrailingComma;function l(){for(;;){var v=n.scan();switch(n.getTokenError()){case 4:p(14);break;case 5:p(15);break;case 3:p(13);break;case 1:d||p(11);break;case 2:p(12);break;case 6:p(16);break}switch(v){case 12:case 13:d?p(10):o();break;case 16:p(1);break;case 15:case 14:break;default:return v}}}function p(v,T,x){if(T===void 0&&(T=[]),x===void 0&&(x=[]),A(v),T.length+x.length>0)for(var I=n.getToken();I!==17;){if(T.indexOf(I)!==-1){l();break}else if(x.indexOf(I)!==-1)break;I=l()}}function i(v){var T=n.getTokenValue();return v?L(T):O(T),l(),!0}function c(){switch(n.getToken()){case 11:var v=n.getTokenValue(),T=Number(v);isNaN(T)&&(p(2),T=0),L(T);break;case 7:L(null);break;case 8:L(!0);break;case 9:L(!1);break;default:return!1}return l(),!0}function e(){return n.getToken()!==10?(p(3,[],[2,5]),!1):(i(!1),n.getToken()===6?(w(":"),l(),y()||p(4,[],[2,5])):p(5,[],[2,5]),!0)}function t(){_(),l();for(var v=!1;n.getToken()!==2&&n.getToken()!==17;){if(n.getToken()===5){if(v||p(4,[],[]),w(","),l(),n.getToken()===2&&u)break}else v&&p(6,[],[]);e()||p(4,[],[2,5]),v=!0}return N(),n.getToken()!==2?p(7,[2],[]):l(),!0}function r(){s(),l();for(var v=!1;n.getToken()!==4&&n.getToken()!==17;){if(n.getToken()===5){if(v||p(4,[],[]),w(","),l(),n.getToken()===4&&u)break}else v&&p(6,[],[]);y()||p(4,[],[4,5]),v=!0}return m(),n.getToken()!==4?p(8,[4],[]):l(),!0}function y(){switch(n.getToken()){case 3:return r();case 1:return t();case 10:return i(!0);default:return c()}}return l(),n.getToken()===17?j.allowEmptyContent?!0:(p(4,[],[]),!1):y()?(n.getToken()!==17&&p(9,[],[]),!0):(p(4,[],[]),!1)}var Ve=ze;const Ke=typeof self<"u"&&typeof self.WorkerGlobalScope<"u",Ce=Ke||typeof window<"u"&&typeof window.document<"u"&&typeof fetch<"u";let Le="",we="";function dt(h){Le=h}function Xe(h){we=h}let ke=null;async function Je(){if(!ke){let h;if(Ce)typeof we=="string"?h=_e.loadWASM({data:await fetch(Oe("dist/onig.wasm")).then(b=>b.arrayBuffer())}):h=_e.loadWASM(we);else{const j=require("path").join(require.resolve("vscode-oniguruma"),"../onig.wasm"),S=require("fs").readFileSync(j).buffer;h=_e.loadWASM(S)}ke=h.then(()=>({createOnigScanner(b){return _e.createOnigScanner(b)},createOnigString(b){return _e.createOnigString(b)}}))}return ke}function Oe(h){if(Ce)return Le||console.warn("[Shiki] no CDN provider found, use `setCDN()` to specify the CDN for loading the resources before calling `getHighlighter()`"),`${Le}${h}`;{const b=require("path");return b.isAbsolute(h)?h:b.resolve(__dirname,"..",h)}}async function Ye(h){const b=Oe(h);return Ce?await fetch(b).then(j=>j.text()):await require("fs").promises.readFile(b,"utf-8")}async function Ee(h){const b=[],j=Ve(await Ye(h),b,{allowTrailingComma:!0});if(b.length)throw b[0];return j}async function Me(h){let b=await Ee(h);const j=De(b);if(j.include){const n=await Me($e(Fe(h),j.include));n.settings&&(j.settings=n.settings.concat(j.settings)),n.bg&&!j.bg&&(j.bg=n.bg),n.colors&&(j.colors=Object.assign(Object.assign({},n.colors),j.colors)),delete j.include}return j}async function Qe(h){return await Ee(h)}function Ze(h){h.settings||(h.settings=[]),!(h.settings[0]&&h.settings[0].settings&&!h.settings[0].scope)&&h.settings.unshift({settings:{foreground:h.fg,background:h.bg}})}function De(h){const b=h.type||"dark",j=Object.assign(Object.assign({name:h.name,type:b},h),et(h));return h.include&&(j.include=h.include),h.tokenColors&&(j.settings=h.tokenColors,delete j.tokenColors),Ze(j),j}const Ne={light:"#333333",dark:"#bbbbbb"},Pe={light:"#fffffe",dark:"#1e1e1e"};function et(h){var b,j,n,S,k,_;let O,N,s=h.settings?h.settings:h.tokenColors;const m=s?s.find(L=>!L.name&&!L.scope):void 0;return!((b=m==null?void 0:m.settings)===null||b===void 0)&&b.foreground&&(O=m.settings.foreground),!((j=m==null?void 0:m.settings)===null||j===void 0)&&j.background&&(N=m.settings.background),!O&&(!((S=(n=h)===null||n===void 0?void 0:n.colors)===null||S===void 0)&&S["editor.foreground"])&&(O=h.colors["editor.foreground"]),!N&&(!((_=(k=h)===null||k===void 0?void 0:k.colors)===null||_===void 0)&&_["editor.background"])&&(N=h.colors["editor.background"]),O||(O=h.type==="light"?Ne.light:Ne.dark),N||(N=h.type==="light"?Pe.light:Pe.dark),{fg:O,bg:N}}class tt{constructor(b,j){this.languagesPath="languages/",this.languageMap={},this.scopeToLangMap={},this._onigLibPromise=b,this._onigLibName=j}get onigLib(){return this._onigLibPromise}getOnigLibName(){return this._onigLibName}getLangRegistration(b){return this.languageMap[b]}async loadGrammar(b){const j=this.scopeToLangMap[b];if(!j)return null;if(j.grammar)return j.grammar;const n=await Qe(je.includes(j)?`${this.languagesPath}${j.path}`:j.path);return j.grammar=n,n}addLanguage(b){this.languageMap[b.id]=b,b.aliases&&b.aliases.forEach(j=>{this.languageMap[j]=b}),this.scopeToLangMap[b.scopeName]=b}}function nt(h,b,j,n,S){let k=j.split(/\r\n|\r|\n/),_=Ie.INITIAL,O=[],N=[];for(let s=0,m=k.length;s=0&&k>=0;)Te(b[S],n[k])&&S--,k--;return S===-1}function at(h,b,j){let n=[],S=0;for(let k=0,_=h.settings.length;k<_;k++){let O=h.settings[k],N;if(typeof O.scope=="string")N=O.scope.split(/,/).map(s=>s.trim());else if(Array.isArray(O.scope))N=O.scope;else continue;for(let s=0,m=N.length;s_.line);let k="";return k+=`
`,b.langId&&(k+=`
${b.langId}
`),k+="",h.forEach((_,O)=>{var N;const s=O+1,m=(N=S.get(s))!==null&&N!==void 0?N:[],L=ut(m).join(" ");k+=``,_.forEach(w=>{const o=[`color: ${w.color||b.fg}`];w.fontStyle&me.Italic&&o.push("font-style: italic"),w.fontStyle&me.Bold&&o.push("font-weight: bold"),w.fontStyle&me.Underline&&o.push("text-decoration: underline"),k+=`${ct(w.content)}`}),k+=` +`}),k=k.replace(/\n*$/,""),k+="
",k}const it={"&":"&","<":"<",">":">",'"':""","'":"'"};function ct(h){return h.replace(/[&<>"']/g,b=>it[b])}function ut(h){var b;const j=new Set(["line"]);for(const n of h)for(const S of(b=n.classes)!==null&&b!==void 0?b:[])j.add(S);return Array.from(j)}class lt extends Ie.Registry{constructor(b){super(b),this._resolver=b,this.themesPath="themes/",this._resolvedThemes={},this._resolvedGrammars={}}getTheme(b){return typeof b=="string"?this._resolvedThemes[b]:b}async loadTheme(b){return typeof b=="string"?(this._resolvedThemes[b]||(this._resolvedThemes[b]=await Me(`${this.themesPath}${b}.json`)),this._resolvedThemes[b]):(b=De(b),b.name&&(this._resolvedThemes[b.name]=b),b)}async loadThemes(b){return await Promise.all(b.map(j=>this.loadTheme(j)))}getLoadedThemes(){return Object.keys(this._resolvedThemes)}getGrammar(b){return this._resolvedGrammars[b]}async loadLanguage(b){const j=await this.loadGrammar(b.scopeName);this._resolvedGrammars[b.id]=j,b.aliases&&b.aliases.forEach(n=>{this._resolvedGrammars[n]=j})}async loadLanguages(b){for(const j of b)this._resolver.addLanguage(j);for(const j of b)await this.loadLanguage(j)}getLoadedLanguages(){return Object.keys(this._resolvedGrammars)}}function Ge(h){return typeof h=="string"?je.find(b=>{var j;return b.id===h||((j=b.aliases)===null||j===void 0?void 0:j.includes(h))}):h}function pt(h){var b;let j=je,n=h.themes||[];return!((b=h.langs)===null||b===void 0)&&b.length&&(j=h.langs.map(Ge)),h.theme&&n.unshift(h.theme),n.length||(n=["nord"]),{_languages:j,_themes:n}}async function gt(h){var b,j;const{_languages:n,_themes:S}=pt(h),k=new tt(Je(),"vscode-oniguruma"),_=new lt(k);!((b=h.paths)===null||b===void 0)&&b.themes&&(_.themesPath=h.paths.themes),!((j=h.paths)===null||j===void 0)&&j.languages&&(k.languagesPath=h.paths.languages);const N=(await _.loadThemes(S))[0];let s;await _.loadLanguages(n);const m={"#000001":"var(--shiki-color-text)","#000002":"var(--shiki-color-background)","#000004":"var(--shiki-token-constant)","#000005":"var(--shiki-token-string)","#000006":"var(--shiki-token-comment)","#000007":"var(--shiki-token-keyword)","#000008":"var(--shiki-token-parameter)","#000009":"var(--shiki-token-function)","#000010":"var(--shiki-token-string-expression)","#000011":"var(--shiki-token-punctuation)","#000012":"var(--shiki-token-link)"};function L(t,r){t.bg=m[t.bg]||t.bg,t.fg=m[t.fg]||t.fg,r.forEach((y,v)=>{r[v]=m[y]||y})}function w(t){const r=t?_.getTheme(t):N;if(!r)throw Error(`No theme registration for ${t}`);(!s||s.name!==r.name)&&(_.setTheme(r),s=r);const y=_.getColorMap();return r.name==="css-variables"&&L(r,y),{_theme:r,_colorMap:y}}function o(t){const r=_.getGrammar(t);if(!r)throw Error(`No language registration for ${t}`);return{_grammar:r}}function A(t,r="text",y,v={includeExplanation:!0}){if(ht(r))return[...t.split(/\r\n|\r|\n/).map(K=>[{content:K}])];const{_grammar:T}=o(r),{_theme:x,_colorMap:I}=w(y);return nt(x,I,t,T,v)}function d(t,r="text",y){let v;typeof r=="object"?v=r:v={lang:r,theme:y};const T=A(t,v.lang,v.theme,{includeExplanation:!1}),{_theme:x}=w(v.theme);return ot(T,{fg:x.fg,bg:x.bg,lineOptions:v==null?void 0:v.lineOptions})}async function u(t){await _.loadTheme(t)}async function l(t){const r=Ge(t);k.addLanguage(r),await _.loadLanguage(r)}function p(){return _.getLoadedThemes()}function i(){return _.getLoadedLanguages()}function c(t){const{_theme:r}=w(t);return r.bg}function e(t){const{_theme:r}=w(t);return r.fg}return{codeToThemedTokens:A,codeToHtml:d,getTheme:t=>w(t)._theme,loadTheme:u,loadLanguage:l,getBackgroundColor:c,getForegroundColor:e,getLoadedThemes:p,getLoadedLanguages:i}}function ht(h){return!h||["plaintext","txt","text"].includes(h)}function mt(h){Xe(h)}export{je as BUNDLED_LANGUAGES,ft as BUNDLED_THEMES,me as FontStyle,gt as getHighlighter,Me as loadTheme,ot as renderToHtml,dt as setCDN,mt as setOnigasmWASM,Xe as setWasm,De as toShikiTheme}; diff --git a/assets/chunks/theme.h7w36Qzb.js b/assets/chunks/theme.h7w36Qzb.js new file mode 100644 index 000000000..40bb9ea28 --- /dev/null +++ b/assets/chunks/theme.h7w36Qzb.js @@ -0,0 +1,7 @@ +import{d as g,o as a,c as l,r as u,n as N,a as H,t as S,_ as m,b as k,w as h,e as f,T as ce,u as ze,i as Ee,l as De,f as ue,g as b,h as T,j as U,k as c,m as i,p as E,q as D,s as x,v as K,x as re,y as G,z as ee,A as te,B as we,C as Fe,D as j,F as A,E as B,G as de,H as Y,I as _,J as O,K as Pe,L as se,M as X,N as ne,O as Oe,P as xe,Q as Ve,R as Ue,S as Ge,U as je,V as qe,W as Re,X as Ke,Y as Le,Z as Se,$ as We,a0 as Ye,a1 as Je,a2 as Xe}from"./framework.g9eZ-ZSs.js";const Ze=g({__name:"VPBadge",props:{text:{},type:{default:"tip"}},setup(s){return(e,t)=>(a(),l("span",{class:N(["VPBadge",e.type])},[u(e.$slots,"default",{},()=>[H(S(e.text),1)],!0)],2))}}),Qe=m(Ze,[["__scopeId","data-v-ea5b2908"]]),et={key:0,class:"VPBackdrop"},tt=g({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(s){return(e,t)=>(a(),k(ce,{name:"fade"},{default:h(()=>[e.show?(a(),l("div",et)):f("",!0)]),_:1}))}}),st=m(tt,[["__scopeId","data-v-54a304ca"]]),y=ze;function nt(s,e){let t,n=!1;return()=>{t&&clearTimeout(t),n?t=setTimeout(s,e):(s(),(n=!0)&&setTimeout(()=>n=!1,e))}}function ie(s){return/^\//.test(s)?s:`/${s}`}function ve(s){const{pathname:e,search:t,hash:n,protocol:o}=new URL(s,"http://a.com");if(Ee(s)||s.startsWith("#")||!o.startsWith("http")||/\.(?!html|md)\w+($|\?)/i.test(s)&&De(s))return s;const{site:r}=y(),d=e.endsWith("/")||e.endsWith(".html")?s:s.replace(/(?:(^\.+)\/)?.*$/,`$1${e.replace(/(\.md)?$/,r.value.cleanUrls?"":".html")}${t}${n}`);return ue(d)}function J({removeCurrent:s=!0,correspondingLink:e=!1}={}){const{site:t,localeIndex:n,page:o,theme:r}=y(),d=b(()=>{var v,$;return{label:(v=t.value.locales[n.value])==null?void 0:v.label,link:(($=t.value.locales[n.value])==null?void 0:$.link)||(n.value==="root"?"/":`/${n.value}/`)}});return{localeLinks:b(()=>Object.entries(t.value.locales).flatMap(([v,$])=>s&&d.value.label===$.label?[]:{text:$.label,link:ot($.link||(v==="root"?"/":`/${v}/`),r.value.i18nRouting!==!1&&e,o.value.relativePath.slice(d.value.link.length-1),!t.value.cleanUrls)})),currentLang:d}}function ot(s,e,t,n){return e?s.replace(/\/$/,"")+ie(t.replace(/(^|\/)index\.md$/,"$1").replace(/\.md$/,n?".html":"")):s}const at=s=>(E("data-v-b9c0c15a"),s=s(),D(),s),rt={class:"NotFound"},it={class:"code"},lt={class:"title"},ct=at(()=>c("div",{class:"divider"},null,-1)),ut={class:"quote"},dt={class:"action"},vt=["href","aria-label"],ht=g({__name:"NotFound",setup(s){const{site:e,theme:t}=y(),{localeLinks:n}=J({removeCurrent:!1}),o=T("/");return U(()=>{var d;const r=window.location.pathname.replace(e.value.base,"").replace(/(^.*?\/).*$/,"/$1");n.value.length&&(o.value=((d=n.value.find(({link:p})=>p.startsWith(r)))==null?void 0:d.link)||n.value[0].link)}),(r,d)=>{var p,v,$,w,M;return a(),l("div",rt,[c("p",it,S(((p=i(t).notFound)==null?void 0:p.code)??"404"),1),c("h1",lt,S(((v=i(t).notFound)==null?void 0:v.title)??"PAGE NOT FOUND"),1),ct,c("blockquote",ut,S((($=i(t).notFound)==null?void 0:$.quote)??"But if you don't change your direction, and if you keep looking, you may end up where you are heading."),1),c("div",dt,[c("a",{class:"link",href:i(ue)(o.value),"aria-label":((w=i(t).notFound)==null?void 0:w.linkLabel)??"go to home"},S(((M=i(t).notFound)==null?void 0:M.linkText)??"Take me home"),9,vt)])])}}}),pt=m(ht,[["__scopeId","data-v-b9c0c15a"]]);function Me(s,e){if(Array.isArray(s))return Z(s);if(s==null)return[];e=ie(e);const t=Object.keys(s).sort((o,r)=>r.split("/").length-o.split("/").length).find(o=>e.startsWith(ie(o))),n=t?s[t]:[];return Array.isArray(n)?Z(n):Z(n.items,n.base)}function _t(s){const e=[];let t=0;for(const n in s){const o=s[n];if(o.items){t=e.push(o);continue}e[t]||e.push({items:[]}),e[t].items.push(o)}return e}function ft(s){const e=[];function t(n){for(const o of n)o.text&&o.link&&e.push({text:o.text,link:o.link,docFooterText:o.docFooterText}),o.items&&t(o.items)}return t(s),e}function le(s,e){return Array.isArray(e)?e.some(t=>le(s,t)):x(s,e.link)?!0:e.items?le(s,e.items):!1}function Z(s,e){return[...s].map(t=>{const n={...t},o=n.base||e;return o&&n.link&&(n.link=o+n.link),n.items&&(n.items=Z(n.items,o)),n})}function F(){const{frontmatter:s,page:e,theme:t}=y(),n=re("(min-width: 960px)"),o=T(!1),r=b(()=>{const I=t.value.sidebar,L=e.value.relativePath;return I?Me(I,L):[]}),d=T(r.value);G(r,(I,L)=>{JSON.stringify(I)!==JSON.stringify(L)&&(d.value=r.value)});const p=b(()=>s.value.sidebar!==!1&&d.value.length>0&&s.value.layout!=="home"),v=b(()=>$?s.value.aside==null?t.value.aside==="left":s.value.aside==="left":!1),$=b(()=>s.value.layout==="home"?!1:s.value.aside!=null?!!s.value.aside:t.value.aside!==!1),w=b(()=>p.value&&n.value),M=b(()=>p.value?_t(d.value):[]);function C(){o.value=!0}function V(){o.value=!1}function P(){o.value?V():C()}return{isOpen:o,sidebar:d,sidebarGroups:M,hasSidebar:p,hasAside:$,leftAside:v,isSidebarEnabled:w,open:C,close:V,toggle:P}}function mt(s,e){let t;ee(()=>{t=s.value?document.activeElement:void 0}),U(()=>{window.addEventListener("keyup",n)}),te(()=>{window.removeEventListener("keyup",n)});function n(o){o.key==="Escape"&&s.value&&(e(),t==null||t.focus())}}const Ce=T(K?location.hash:"");K&&window.addEventListener("hashchange",()=>{Ce.value=location.hash});function gt(s){const{page:e}=y(),t=T(!1),n=b(()=>s.value.collapsed!=null),o=b(()=>!!s.value.link),r=T(!1),d=()=>{r.value=x(e.value.relativePath,s.value.link)};G([e,s,Ce],d),U(d);const p=b(()=>r.value?!0:s.value.items?le(e.value.relativePath,s.value.items):!1),v=b(()=>!!(s.value.items&&s.value.items.length));ee(()=>{t.value=!!(n.value&&s.value.collapsed)}),we(()=>{(r.value||p.value)&&(t.value=!1)});function $(){n.value&&(t.value=!t.value)}return{collapsed:t,collapsible:n,isLink:o,isActiveLink:r,hasActiveLink:p,hasChildren:v,toggle:$}}function $t(){const{hasSidebar:s}=F(),e=re("(min-width: 960px)"),t=re("(min-width: 1280px)");return{isAsideEnabled:b(()=>!t.value&&!e.value?!1:s.value?t.value:e.value)}}const kt=71;function he(s){return typeof s.outline=="object"&&!Array.isArray(s.outline)&&s.outline.label||s.outlineTitle||"On this page"}function pe(s){const e=[...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")].filter(t=>t.id&&t.hasChildNodes()).map(t=>{const n=Number(t.tagName[1]);return{title:bt(t),link:"#"+t.id,level:n}});return yt(e,s)}function bt(s){let e="";for(const t of s.childNodes)if(t.nodeType===1){if(t.classList.contains("VPBadge")||t.classList.contains("header-anchor"))continue;e+=t.textContent}else t.nodeType===3&&(e+=t.textContent);return e.trim()}function yt(s,e){if(e===!1)return[];const t=(typeof e=="object"&&!Array.isArray(e)?e.level:e)||2,[n,o]=typeof t=="number"?[t,t]:t==="deep"?[2,6]:t;s=s.filter(d=>d.level>=n&&d.level<=o);const r=[];e:for(let d=0;d=0;v--){const $=s[v];if($.level{requestAnimationFrame(r),window.addEventListener("scroll",n)}),Fe(()=>{d(location.hash)}),te(()=>{window.removeEventListener("scroll",n)});function r(){if(!t.value)return;const p=[].slice.call(s.value.querySelectorAll(".outline-link")),v=[].slice.call(document.querySelectorAll(".content .header-anchor")).filter(V=>p.some(P=>P.hash===V.hash&&V.offsetParent!==null)),$=window.scrollY,w=window.innerHeight,M=document.body.offsetHeight,C=Math.abs($+w-M)<1;if(v.length&&C){d(v[v.length-1].hash);return}for(let V=0;V{const o=j("VPDocOutlineItem",!0);return a(),l("ul",{class:N(t.root?"root":"nested")},[(a(!0),l(A,null,B(t.headers,({children:r,link:d,title:p})=>(a(),l("li",null,[c("a",{class:"outline-link",href:d,onClick:e,title:p},S(p),9,Vt),r!=null&&r.length?(a(),k(o,{key:0,headers:r},null,8,["headers"])):f("",!0)]))),256))],2)}}}),_e=m(Lt,[["__scopeId","data-v-463da30f"]]),St=s=>(E("data-v-3a6c4994"),s=s(),D(),s),Mt={class:"content"},Ct={class:"outline-title",role:"heading","aria-level":"2"},It={"aria-labelledby":"doc-outline-aria-label"},Tt=St(()=>c("span",{class:"visually-hidden",id:"doc-outline-aria-label"}," Table of Contents for current page ",-1)),At=g({__name:"VPDocAsideOutline",setup(s){const{frontmatter:e,theme:t}=y(),n=de([]);Y(()=>{n.value=pe(e.value.outline??t.value.outline)});const o=T(),r=T();return wt(o,r),(d,p)=>(a(),l("div",{class:N(["VPDocAsideOutline",{"has-outline":n.value.length>0}]),ref_key:"container",ref:o,role:"navigation"},[c("div",Mt,[c("div",{class:"outline-marker",ref_key:"marker",ref:r},null,512),c("div",Ct,S(i(he)(i(t))),1),c("nav",It,[Tt,_(_e,{headers:n.value,root:!0},null,8,["headers"])])])],2))}}),Nt=m(At,[["__scopeId","data-v-3a6c4994"]]),Bt={class:"VPDocAsideCarbonAds"},Ht=g({__name:"VPDocAsideCarbonAds",props:{carbonAds:{}},setup(s){const e=()=>null;return(t,n)=>(a(),l("div",Bt,[_(i(e),{"carbon-ads":t.carbonAds},null,8,["carbon-ads"])]))}}),zt=s=>(E("data-v-cb998dce"),s=s(),D(),s),Et={class:"VPDocAside"},Dt=zt(()=>c("div",{class:"spacer"},null,-1)),Ft=g({__name:"VPDocAside",setup(s){const{theme:e}=y();return(t,n)=>(a(),l("div",Et,[u(t.$slots,"aside-top",{},void 0,!0),u(t.$slots,"aside-outline-before",{},void 0,!0),_(Nt),u(t.$slots,"aside-outline-after",{},void 0,!0),Dt,u(t.$slots,"aside-ads-before",{},void 0,!0),i(e).carbonAds?(a(),k(Ht,{key:0,"carbon-ads":i(e).carbonAds},null,8,["carbon-ads"])):f("",!0),u(t.$slots,"aside-ads-after",{},void 0,!0),u(t.$slots,"aside-bottom",{},void 0,!0)]))}}),Ot=m(Ft,[["__scopeId","data-v-cb998dce"]]);function xt(){const{theme:s,page:e}=y();return b(()=>{const{text:t="Edit this page",pattern:n=""}=s.value.editLink||{};let o;return typeof n=="function"?o=n(e.value):o=n.replace(/:path/g,e.value.filePath),{url:o,text:t}})}function Ut(){const{page:s,theme:e,frontmatter:t}=y();return b(()=>{var v,$,w,M,C,V,P,I;const n=Me(e.value.sidebar,s.value.relativePath),o=ft(n),r=o.findIndex(L=>x(s.value.relativePath,L.link)),d=((v=e.value.docFooter)==null?void 0:v.prev)===!1&&!t.value.prev||t.value.prev===!1,p=(($=e.value.docFooter)==null?void 0:$.next)===!1&&!t.value.next||t.value.next===!1;return{prev:d?void 0:{text:(typeof t.value.prev=="string"?t.value.prev:typeof t.value.prev=="object"?t.value.prev.text:void 0)??((w=o[r-1])==null?void 0:w.docFooterText)??((M=o[r-1])==null?void 0:M.text),link:(typeof t.value.prev=="object"?t.value.prev.link:void 0)??((C=o[r-1])==null?void 0:C.link)},next:p?void 0:{text:(typeof t.value.next=="string"?t.value.next:typeof t.value.next=="object"?t.value.next.text:void 0)??((V=o[r+1])==null?void 0:V.docFooterText)??((P=o[r+1])==null?void 0:P.text),link:(typeof t.value.next=="object"?t.value.next.link:void 0)??((I=o[r+1])==null?void 0:I.link)}}})}const Gt={},jt={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},qt=c("path",{d:"M18,23H4c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h7c0.6,0,1,0.4,1,1s-0.4,1-1,1H4C3.4,5,3,5.4,3,6v14c0,0.6,0.4,1,1,1h14c0.6,0,1-0.4,1-1v-7c0-0.6,0.4-1,1-1s1,0.4,1,1v7C21,21.7,19.7,23,18,23z"},null,-1),Rt=c("path",{d:"M8,17c-0.3,0-0.5-0.1-0.7-0.3C7,16.5,6.9,16.1,7,15.8l1-4c0-0.2,0.1-0.3,0.3-0.5l9.5-9.5c1.2-1.2,3.2-1.2,4.4,0c1.2,1.2,1.2,3.2,0,4.4l-9.5,9.5c-0.1,0.1-0.3,0.2-0.5,0.3l-4,1C8.2,17,8.1,17,8,17zM9.9,12.5l-0.5,2.1l2.1-0.5l9.3-9.3c0.4-0.4,0.4-1.1,0-1.6c-0.4-0.4-1.2-0.4-1.6,0l0,0L9.9,12.5z M18.5,2.5L18.5,2.5L18.5,2.5z"},null,-1),Kt=[qt,Rt];function Wt(s,e){return a(),l("svg",jt,Kt)}const Yt=m(Gt,[["render",Wt]]),z=g({__name:"VPLink",props:{tag:{},href:{},noIcon:{type:Boolean},target:{},rel:{}},setup(s){const e=s,t=b(()=>e.tag??(e.href?"a":"span")),n=b(()=>e.href&&Pe.test(e.href));return(o,r)=>(a(),k(O(t.value),{class:N(["VPLink",{link:o.href,"vp-external-link-icon":n.value,"no-icon":o.noIcon}]),href:o.href?i(ve)(o.href):void 0,target:o.target??(n.value?"_blank":void 0),rel:o.rel??(n.value?"noreferrer":void 0)},{default:h(()=>[u(o.$slots,"default")]),_:3},8,["class","href","target","rel"]))}}),Jt={class:"VPLastUpdated"},Xt=["datetime"],Zt=g({__name:"VPDocFooterLastUpdated",setup(s){const{theme:e,page:t,frontmatter:n,lang:o}=y(),r=b(()=>new Date(n.value.lastUpdated??t.value.lastUpdated)),d=b(()=>r.value.toISOString()),p=T("");return U(()=>{ee(()=>{var v,$,w;p.value=new Intl.DateTimeFormat(($=(v=e.value.lastUpdated)==null?void 0:v.formatOptions)!=null&&$.forceLocale?o.value:void 0,((w=e.value.lastUpdated)==null?void 0:w.formatOptions)??{dateStyle:"short",timeStyle:"short"}).format(r.value)})}),(v,$)=>{var w;return a(),l("p",Jt,[H(S(((w=i(e).lastUpdated)==null?void 0:w.text)||i(e).lastUpdatedText||"Last updated")+": ",1),c("time",{datetime:d.value},S(p.value),9,Xt)])}}}),Qt=m(Zt,[["__scopeId","data-v-19a7ae4e"]]),es={key:0,class:"VPDocFooter"},ts={key:0,class:"edit-info"},ss={key:0,class:"edit-link"},ns={key:1,class:"last-updated"},os={key:1,class:"prev-next"},as={class:"pager"},rs=["innerHTML"],is=["innerHTML"],ls={class:"pager"},cs=["innerHTML"],us=["innerHTML"],ds=g({__name:"VPDocFooter",setup(s){const{theme:e,page:t,frontmatter:n}=y(),o=xt(),r=Ut(),d=b(()=>e.value.editLink&&n.value.editLink!==!1),p=b(()=>t.value.lastUpdated&&n.value.lastUpdated!==!1),v=b(()=>d.value||p.value||r.value.prev||r.value.next);return($,w)=>{var M,C,V,P;return v.value?(a(),l("footer",es,[u($.$slots,"doc-footer-before",{},void 0,!0),d.value||p.value?(a(),l("div",ts,[d.value?(a(),l("div",ss,[_(z,{class:"edit-link-button",href:i(o).url,"no-icon":!0},{default:h(()=>[_(Yt,{class:"edit-link-icon","aria-label":"edit icon"}),H(" "+S(i(o).text),1)]),_:1},8,["href"])])):f("",!0),p.value?(a(),l("div",ns,[_(Qt)])):f("",!0)])):f("",!0),(M=i(r).prev)!=null&&M.link||(C=i(r).next)!=null&&C.link?(a(),l("nav",os,[c("div",as,[(V=i(r).prev)!=null&&V.link?(a(),k(z,{key:0,class:"pager-link prev",href:i(r).prev.link},{default:h(()=>{var I;return[c("span",{class:"desc",innerHTML:((I=i(e).docFooter)==null?void 0:I.prev)||"Previous page"},null,8,rs),c("span",{class:"title",innerHTML:i(r).prev.text},null,8,is)]}),_:1},8,["href"])):f("",!0)]),c("div",ls,[(P=i(r).next)!=null&&P.link?(a(),k(z,{key:0,class:"pager-link next",href:i(r).next.link},{default:h(()=>{var I;return[c("span",{class:"desc",innerHTML:((I=i(e).docFooter)==null?void 0:I.next)||"Next page"},null,8,cs),c("span",{class:"title",innerHTML:i(r).next.text},null,8,us)]}),_:1},8,["href"])):f("",!0)])])):f("",!0)])):f("",!0)}}}),vs=m(ds,[["__scopeId","data-v-b4b63abf"]]),hs={},ps={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},_s=c("path",{d:"M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z"},null,-1),fs=[_s];function ms(s,e){return a(),l("svg",ps,fs)}const fe=m(hs,[["render",ms]]),gs={key:0,class:"VPDocOutlineDropdown"},$s={key:0,class:"items"},ks=g({__name:"VPDocOutlineDropdown",setup(s){const{frontmatter:e,theme:t}=y(),n=T(!1);Y(()=>{n.value=!1});const o=de([]);return Y(()=>{o.value=pe(e.value.outline??t.value.outline)}),(r,d)=>o.value.length>0?(a(),l("div",gs,[c("button",{onClick:d[0]||(d[0]=p=>n.value=!n.value),class:N({open:n.value})},[H(S(i(he)(i(t)))+" ",1),_(fe,{class:"icon"})],2),n.value?(a(),l("div",$s,[_(_e,{headers:o.value},null,8,["headers"])])):f("",!0)])):f("",!0)}}),bs=m(ks,[["__scopeId","data-v-95bb0785"]]),ys=s=>(E("data-v-a3c25e27"),s=s(),D(),s),ws={class:"container"},Ps=ys(()=>c("div",{class:"aside-curtain"},null,-1)),Vs={class:"aside-container"},Ls={class:"aside-content"},Ss={class:"content"},Ms={class:"content-container"},Cs={class:"main"},Is=g({__name:"VPDoc",setup(s){const{theme:e}=y(),t=se(),{hasSidebar:n,hasAside:o,leftAside:r}=F(),d=b(()=>t.path.replace(/[./]+/g,"_").replace(/_html$/,""));return(p,v)=>{const $=j("Content");return a(),l("div",{class:N(["VPDoc",{"has-sidebar":i(n),"has-aside":i(o)}])},[u(p.$slots,"doc-top",{},void 0,!0),c("div",ws,[i(o)?(a(),l("div",{key:0,class:N(["aside",{"left-aside":i(r)}])},[Ps,c("div",Vs,[c("div",Ls,[_(Ot,null,{"aside-top":h(()=>[u(p.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":h(()=>[u(p.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":h(()=>[u(p.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":h(()=>[u(p.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":h(()=>[u(p.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":h(()=>[u(p.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])],2)):f("",!0),c("div",Ss,[c("div",Ms,[u(p.$slots,"doc-before",{},void 0,!0),_(bs),c("main",Cs,[_($,{class:N(["vp-doc",[d.value,i(e).externalLinkIcon&&"external-link-icon-enabled"]])},null,8,["class"])]),_(vs,null,{"doc-footer-before":h(()=>[u(p.$slots,"doc-footer-before",{},void 0,!0)]),_:3}),u(p.$slots,"doc-after",{},void 0,!0)])])]),u(p.$slots,"doc-bottom",{},void 0,!0)],2)}}}),Ts=m(Is,[["__scopeId","data-v-a3c25e27"]]),As=g({__name:"VPButton",props:{tag:{},size:{default:"medium"},theme:{default:"brand"},text:{},href:{}},setup(s){const e=s,t=b(()=>e.href&&Pe.test(e.href)),n=b(()=>e.tag||e.href?"a":"button");return(o,r)=>(a(),k(O(n.value),{class:N(["VPButton",[o.size,o.theme]]),href:o.href?i(ve)(o.href):void 0,target:t.value?"_blank":void 0,rel:t.value?"noreferrer":void 0},{default:h(()=>[H(S(o.text),1)]),_:1},8,["class","href","target","rel"]))}}),Ns=m(As,[["__scopeId","data-v-1e76fe75"]]),Bs=["src","alt"],Hs=g({inheritAttrs:!1,__name:"VPImage",props:{image:{},alt:{}},setup(s){return(e,t)=>{const n=j("VPImage",!0);return e.image?(a(),l(A,{key:0},[typeof e.image=="string"||"src"in e.image?(a(),l("img",X({key:0,class:"VPImage"},typeof e.image=="string"?e.$attrs:{...e.image,...e.$attrs},{src:i(ue)(typeof e.image=="string"?e.image:e.image.src),alt:e.alt??(typeof e.image=="string"?"":e.image.alt||"")}),null,16,Bs)):(a(),l(A,{key:1},[_(n,X({class:"dark",image:e.image.dark,alt:e.image.alt},e.$attrs),null,16,["image","alt"]),_(n,X({class:"light",image:e.image.light,alt:e.image.alt},e.$attrs),null,16,["image","alt"])],64))],64)):f("",!0)}}}),Q=m(Hs,[["__scopeId","data-v-ab19afbb"]]),zs=s=>(E("data-v-5a3e9999"),s=s(),D(),s),Es={class:"container"},Ds={class:"main"},Fs={key:0,class:"name"},Os=["innerHTML"],xs=["innerHTML"],Us=["innerHTML"],Gs={key:0,class:"actions"},js={key:0,class:"image"},qs={class:"image-container"},Rs=zs(()=>c("div",{class:"image-bg"},null,-1)),Ks=g({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(s){const e=ne("hero-image-slot-exists");return(t,n)=>(a(),l("div",{class:N(["VPHero",{"has-image":t.image||i(e)}])},[c("div",Es,[c("div",Ds,[u(t.$slots,"home-hero-info",{},()=>[t.name?(a(),l("h1",Fs,[c("span",{innerHTML:t.name,class:"clip"},null,8,Os)])):f("",!0),t.text?(a(),l("p",{key:1,innerHTML:t.text,class:"text"},null,8,xs)):f("",!0),t.tagline?(a(),l("p",{key:2,innerHTML:t.tagline,class:"tagline"},null,8,Us)):f("",!0)],!0),t.actions?(a(),l("div",Gs,[(a(!0),l(A,null,B(t.actions,o=>(a(),l("div",{key:o.link,class:"action"},[_(Ns,{tag:"a",size:"medium",theme:o.theme,text:o.text,href:o.link},null,8,["theme","text","href"])]))),128))])):f("",!0)]),t.image||i(e)?(a(),l("div",js,[c("div",qs,[Rs,u(t.$slots,"home-hero-image",{},()=>[t.image?(a(),k(Q,{key:0,class:"image-src",image:t.image},null,8,["image"])):f("",!0)],!0)])])):f("",!0)])],2))}}),Ws=m(Ks,[["__scopeId","data-v-5a3e9999"]]),Ys=g({__name:"VPHomeHero",setup(s){const{frontmatter:e}=y();return(t,n)=>i(e).hero?(a(),k(Ws,{key:0,class:"VPHomeHero",name:i(e).hero.name,text:i(e).hero.text,tagline:i(e).hero.tagline,image:i(e).hero.image,actions:i(e).hero.actions},{"home-hero-info":h(()=>[u(t.$slots,"home-hero-info")]),"home-hero-image":h(()=>[u(t.$slots,"home-hero-image")]),_:3},8,["name","text","tagline","image","actions"])):f("",!0)}}),Js={},Xs={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},Zs=c("path",{d:"M19.9,12.4c0.1-0.2,0.1-0.5,0-0.8c-0.1-0.1-0.1-0.2-0.2-0.3l-7-7c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.3,5.3H5c-0.6,0-1,0.4-1,1s0.4,1,1,1h11.6l-5.3,5.3c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3l7-7C19.8,12.6,19.9,12.5,19.9,12.4z"},null,-1),Qs=[Zs];function en(s,e){return a(),l("svg",Xs,Qs)}const tn=m(Js,[["render",en]]),sn={class:"box"},nn={key:0,class:"icon"},on=["innerHTML"],an=["innerHTML"],rn=["innerHTML"],ln={key:4,class:"link-text"},cn={class:"link-text-value"},un=g({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{},rel:{},target:{}},setup(s){return(e,t)=>(a(),k(z,{class:"VPFeature",href:e.link,rel:e.rel,target:e.target,"no-icon":!0,tag:e.link?"a":"div"},{default:h(()=>[c("article",sn,[typeof e.icon=="object"&&e.icon.wrap?(a(),l("div",nn,[_(Q,{image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])])):typeof e.icon=="object"?(a(),k(Q,{key:1,image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])):e.icon?(a(),l("div",{key:2,class:"icon",innerHTML:e.icon},null,8,on)):f("",!0),c("h2",{class:"title",innerHTML:e.title},null,8,an),e.details?(a(),l("p",{key:3,class:"details",innerHTML:e.details},null,8,rn)):f("",!0),e.linkText?(a(),l("div",ln,[c("p",cn,[H(S(e.linkText)+" ",1),_(tn,{class:"link-text-icon"})])])):f("",!0)])]),_:1},8,["href","rel","target","tag"]))}}),dn=m(un,[["__scopeId","data-v-ee984185"]]),vn={key:0,class:"VPFeatures"},hn={class:"container"},pn={class:"items"},_n=g({__name:"VPFeatures",props:{features:{}},setup(s){const e=s,t=b(()=>{const n=e.features.length;if(n){if(n===2)return"grid-2";if(n===3)return"grid-3";if(n%3===0)return"grid-6";if(n>3)return"grid-4"}else return});return(n,o)=>n.features?(a(),l("div",vn,[c("div",hn,[c("div",pn,[(a(!0),l(A,null,B(n.features,r=>(a(),l("div",{key:r.title,class:N(["item",[t.value]])},[_(dn,{icon:r.icon,title:r.title,details:r.details,link:r.link,"link-text":r.linkText,rel:r.rel,target:r.target},null,8,["icon","title","details","link","link-text","rel","target"])],2))),128))])])])):f("",!0)}}),fn=m(_n,[["__scopeId","data-v-b1eea84a"]]),mn=g({__name:"VPHomeFeatures",setup(s){const{frontmatter:e}=y();return(t,n)=>i(e).features?(a(),k(fn,{key:0,class:"VPHomeFeatures",features:i(e).features},null,8,["features"])):f("",!0)}}),gn={class:"VPHome"},$n=g({__name:"VPHome",setup(s){return(e,t)=>{const n=j("Content");return a(),l("div",gn,[u(e.$slots,"home-hero-before",{},void 0,!0),_(Ys,null,{"home-hero-info":h(()=>[u(e.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-image":h(()=>[u(e.$slots,"home-hero-image",{},void 0,!0)]),_:3}),u(e.$slots,"home-hero-after",{},void 0,!0),u(e.$slots,"home-features-before",{},void 0,!0),_(mn),u(e.$slots,"home-features-after",{},void 0,!0),_(n)])}}}),kn=m($n,[["__scopeId","data-v-20eabd3a"]]),bn={},yn={class:"VPPage"};function wn(s,e){const t=j("Content");return a(),l("div",yn,[u(s.$slots,"page-top"),_(t),u(s.$slots,"page-bottom")])}const Pn=m(bn,[["render",wn]]),Vn=g({__name:"VPContent",setup(s){const{page:e,frontmatter:t}=y(),{hasSidebar:n}=F();return(o,r)=>(a(),l("div",{class:N(["VPContent",{"has-sidebar":i(n),"is-home":i(t).layout==="home"}]),id:"VPContent"},[i(e).isNotFound?u(o.$slots,"not-found",{key:0},()=>[_(pt)],!0):i(t).layout==="page"?(a(),k(Pn,{key:1},{"page-top":h(()=>[u(o.$slots,"page-top",{},void 0,!0)]),"page-bottom":h(()=>[u(o.$slots,"page-bottom",{},void 0,!0)]),_:3})):i(t).layout==="home"?(a(),k(kn,{key:2},{"home-hero-before":h(()=>[u(o.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info":h(()=>[u(o.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-image":h(()=>[u(o.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":h(()=>[u(o.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":h(()=>[u(o.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":h(()=>[u(o.$slots,"home-features-after",{},void 0,!0)]),_:3})):i(t).layout&&i(t).layout!=="doc"?(a(),k(O(i(t).layout),{key:3})):(a(),k(Ts,{key:4},{"doc-top":h(()=>[u(o.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":h(()=>[u(o.$slots,"doc-bottom",{},void 0,!0)]),"doc-footer-before":h(()=>[u(o.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":h(()=>[u(o.$slots,"doc-before",{},void 0,!0)]),"doc-after":h(()=>[u(o.$slots,"doc-after",{},void 0,!0)]),"aside-top":h(()=>[u(o.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":h(()=>[u(o.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":h(()=>[u(o.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":h(()=>[u(o.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":h(()=>[u(o.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":h(()=>[u(o.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}}),Ln=m(Vn,[["__scopeId","data-v-3cf691b6"]]),Sn={class:"container"},Mn=["innerHTML"],Cn=["innerHTML"],In=g({__name:"VPFooter",setup(s){const{theme:e,frontmatter:t}=y(),{hasSidebar:n}=F();return(o,r)=>i(e).footer&&i(t).footer!==!1?(a(),l("footer",{key:0,class:N(["VPFooter",{"has-sidebar":i(n)}])},[c("div",Sn,[i(e).footer.message?(a(),l("p",{key:0,class:"message",innerHTML:i(e).footer.message},null,8,Mn)):f("",!0),i(e).footer.copyright?(a(),l("p",{key:1,class:"copyright",innerHTML:i(e).footer.copyright},null,8,Cn)):f("",!0)])],2)):f("",!0)}}),Tn=m(In,[["__scopeId","data-v-566314d4"]]),An={class:"header"},Nn={class:"outline"},Bn=g({__name:"VPLocalNavOutlineDropdown",props:{headers:{},navHeight:{}},setup(s){const e=s,{theme:t}=y(),n=T(!1),o=T(0),r=T();Y(()=>{n.value=!1});function d(){n.value=!n.value,o.value=window.innerHeight+Math.min(window.scrollY-e.navHeight,0)}function p($){$.target.classList.contains("outline-link")&&(r.value&&(r.value.style.transition="none"),xe(()=>{n.value=!1}))}function v(){n.value=!1,window.scrollTo({top:0,left:0,behavior:"smooth"})}return($,w)=>(a(),l("div",{class:"VPLocalNavOutlineDropdown",style:Oe({"--vp-vh":o.value+"px"})},[$.headers.length>0?(a(),l("button",{key:0,onClick:d,class:N({open:n.value})},[H(S(i(he)(i(t)))+" ",1),_(fe,{class:"icon"})],2)):(a(),l("button",{key:1,onClick:v},S(i(t).returnToTopLabel||"Return to top"),1)),_(ce,{name:"flyout"},{default:h(()=>[n.value?(a(),l("div",{key:0,ref_key:"items",ref:r,class:"items",onClick:p},[c("div",An,[c("a",{class:"top-link",href:"#",onClick:v},S(i(t).returnToTopLabel||"Return to top"),1)]),c("div",Nn,[_(_e,{headers:$.headers},null,8,["headers"])])],512)):f("",!0)]),_:1})],4))}}),Hn=m(Bn,[["__scopeId","data-v-24251f6f"]]),zn={},En={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Dn=c("path",{d:"M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"},null,-1),Fn=c("path",{d:"M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"},null,-1),On=c("path",{d:"M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"},null,-1),xn=c("path",{d:"M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"},null,-1),Un=[Dn,Fn,On,xn];function Gn(s,e){return a(),l("svg",En,Un)}const jn=m(zn,[["render",Gn]]),qn=["aria-expanded"],Rn={class:"menu-text"},Kn=g({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(s){const{theme:e,frontmatter:t}=y(),{hasSidebar:n}=F(),{y:o}=Ve(),r=de([]),d=T(0);U(()=>{d.value=parseInt(getComputedStyle(document.documentElement).getPropertyValue("--vp-nav-height"))}),Y(()=>{r.value=pe(t.value.outline??e.value.outline)});const p=b(()=>r.value.length===0&&!n.value),v=b(()=>({VPLocalNav:!0,fixed:p.value,"reached-top":o.value>=d.value}));return($,w)=>i(t).layout!=="home"&&(!p.value||i(o)>=d.value)?(a(),l("div",{key:0,class:N(v.value)},[i(n)?(a(),l("button",{key:0,class:"menu","aria-expanded":$.open,"aria-controls":"VPSidebarNav",onClick:w[0]||(w[0]=M=>$.$emit("open-menu"))},[_(jn,{class:"menu-icon"}),c("span",Rn,S(i(e).sidebarMenuLabel||"Menu"),1)],8,qn)):f("",!0),_(Hn,{headers:r.value,navHeight:d.value},null,8,["headers","navHeight"])],2)):f("",!0)}}),Wn=m(Kn,[["__scopeId","data-v-f8a0b38a"]]);function Yn(){const s=T(!1);function e(){s.value=!0,window.addEventListener("resize",o)}function t(){s.value=!1,window.removeEventListener("resize",o)}function n(){s.value?t():e()}function o(){window.outerWidth>=768&&t()}const r=se();return G(()=>r.path,t),{isScreenOpen:s,openScreen:e,closeScreen:t,toggleScreen:n}}const Jn={},Xn={class:"VPSwitch",type:"button",role:"switch"},Zn={class:"check"},Qn={key:0,class:"icon"};function eo(s,e){return a(),l("button",Xn,[c("span",Zn,[s.$slots.default?(a(),l("span",Qn,[u(s.$slots,"default",{},void 0,!0)])):f("",!0)])])}const to=m(Jn,[["render",eo],["__scopeId","data-v-1c29e291"]]),so={},no={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},oo=c("path",{d:"M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"},null,-1),ao=[oo];function ro(s,e){return a(),l("svg",no,ao)}const io=m(so,[["render",ro]]),lo={},co={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},uo=Ue('',9),vo=[uo];function ho(s,e){return a(),l("svg",co,vo)}const po=m(lo,[["render",ho]]),_o=g({__name:"VPSwitchAppearance",setup(s){const{isDark:e}=y(),t=ne("toggle-appearance",()=>{e.value=!e.value}),n=b(()=>e.value?"Switch to light theme":"Switch to dark theme");return(o,r)=>(a(),k(to,{title:n.value,class:"VPSwitchAppearance","aria-checked":i(e),onClick:i(t)},{default:h(()=>[_(po,{class:"sun"}),_(io,{class:"moon"})]),_:1},8,["title","aria-checked","onClick"]))}}),me=m(_o,[["__scopeId","data-v-70af5d02"]]),fo={key:0,class:"VPNavBarAppearance"},mo=g({__name:"VPNavBarAppearance",setup(s){const{site:e}=y();return(t,n)=>i(e).appearance&&i(e).appearance!=="force-dark"?(a(),l("div",fo,[_(me)])):f("",!0)}}),go=m(mo,[["__scopeId","data-v-283b26e9"]]),ge=T();let Ie=!1,ae=0;function $o(s){const e=T(!1);if(K){!Ie&&ko(),ae++;const t=G(ge,n=>{var o,r,d;n===s.el.value||(o=s.el.value)!=null&&o.contains(n)?(e.value=!0,(r=s.onFocus)==null||r.call(s)):(e.value=!1,(d=s.onBlur)==null||d.call(s))});te(()=>{t(),ae--,ae||bo()})}return Ge(e)}function ko(){document.addEventListener("focusin",Te),Ie=!0,ge.value=document.activeElement}function bo(){document.removeEventListener("focusin",Te)}function Te(){ge.value=document.activeElement}const yo={},wo={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Po=c("path",{d:"M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"},null,-1),Vo=[Po];function Lo(s,e){return a(),l("svg",wo,Vo)}const Ae=m(yo,[["render",Lo]]),So={},Mo={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Co=c("circle",{cx:"12",cy:"12",r:"2"},null,-1),Io=c("circle",{cx:"19",cy:"12",r:"2"},null,-1),To=c("circle",{cx:"5",cy:"12",r:"2"},null,-1),Ao=[Co,Io,To];function No(s,e){return a(),l("svg",Mo,Ao)}const Bo=m(So,[["render",No]]),Ho={class:"VPMenuLink"},zo=g({__name:"VPMenuLink",props:{item:{}},setup(s){const{page:e}=y();return(t,n)=>(a(),l("div",Ho,[_(z,{class:N({active:i(x)(i(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel},{default:h(()=>[H(S(t.item.text),1)]),_:1},8,["class","href","target","rel"])]))}}),oe=m(zo,[["__scopeId","data-v-f51f088d"]]),Eo={class:"VPMenuGroup"},Do={key:0,class:"title"},Fo=g({__name:"VPMenuGroup",props:{text:{},items:{}},setup(s){return(e,t)=>(a(),l("div",Eo,[e.text?(a(),l("p",Do,S(e.text),1)):f("",!0),(a(!0),l(A,null,B(e.items,n=>(a(),l(A,null,["link"in n?(a(),k(oe,{key:0,item:n},null,8,["item"])):f("",!0)],64))),256))]))}}),Oo=m(Fo,[["__scopeId","data-v-a6b0397c"]]),xo={class:"VPMenu"},Uo={key:0,class:"items"},Go=g({__name:"VPMenu",props:{items:{}},setup(s){return(e,t)=>(a(),l("div",xo,[e.items?(a(),l("div",Uo,[(a(!0),l(A,null,B(e.items,n=>(a(),l(A,{key:n.text},["link"in n?(a(),k(oe,{key:0,item:n},null,8,["item"])):(a(),k(Oo,{key:1,text:n.text,items:n.items},null,8,["text","items"]))],64))),128))])):f("",!0),u(e.$slots,"default",{},void 0,!0)]))}}),jo=m(Go,[["__scopeId","data-v-e42ed9b3"]]),qo=["aria-expanded","aria-label"],Ro={key:0,class:"text"},Ko=["innerHTML"],Wo={class:"menu"},Yo=g({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(s){const e=T(!1),t=T();$o({el:t,onBlur:n});function n(){e.value=!1}return(o,r)=>(a(),l("div",{class:"VPFlyout",ref_key:"el",ref:t,onMouseenter:r[1]||(r[1]=d=>e.value=!0),onMouseleave:r[2]||(r[2]=d=>e.value=!1)},[c("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":e.value,"aria-label":o.label,onClick:r[0]||(r[0]=d=>e.value=!e.value)},[o.button||o.icon?(a(),l("span",Ro,[o.icon?(a(),k(O(o.icon),{key:0,class:"option-icon"})):f("",!0),o.button?(a(),l("span",{key:1,innerHTML:o.button},null,8,Ko)):f("",!0),_(Ae,{class:"text-icon"})])):(a(),k(Bo,{key:1,class:"icon"}))],8,qo),c("div",Wo,[_(jo,{items:o.items},{default:h(()=>[u(o.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}}),$e=m(Yo,[["__scopeId","data-v-aa8de344"]]),Jo={discord:'Discord',facebook:'Facebook',github:'GitHub',instagram:'Instagram',linkedin:'LinkedIn',mastodon:'Mastodon',slack:'Slack',twitter:'Twitter',x:'X',youtube:'YouTube'},Xo=["href","aria-label","innerHTML"],Zo=g({__name:"VPSocialLink",props:{icon:{},link:{},ariaLabel:{}},setup(s){const e=s,t=b(()=>typeof e.icon=="object"?e.icon.svg:Jo[e.icon]);return(n,o)=>(a(),l("a",{class:"VPSocialLink no-icon",href:n.link,"aria-label":n.ariaLabel??(typeof n.icon=="string"?n.icon:""),target:"_blank",rel:"noopener",innerHTML:t.value},null,8,Xo))}}),Qo=m(Zo,[["__scopeId","data-v-16cf740a"]]),ea={class:"VPSocialLinks"},ta=g({__name:"VPSocialLinks",props:{links:{}},setup(s){return(e,t)=>(a(),l("div",ea,[(a(!0),l(A,null,B(e.links,({link:n,icon:o,ariaLabel:r})=>(a(),k(Qo,{key:n,icon:o,link:n,ariaLabel:r},null,8,["icon","link","ariaLabel"]))),128))]))}}),ke=m(ta,[["__scopeId","data-v-e71e869c"]]),sa={key:0,class:"group translations"},na={class:"trans-title"},oa={key:1,class:"group"},aa={class:"item appearance"},ra={class:"label"},ia={class:"appearance-action"},la={key:2,class:"group"},ca={class:"item social-links"},ua=g({__name:"VPNavBarExtra",setup(s){const{site:e,theme:t}=y(),{localeLinks:n,currentLang:o}=J({correspondingLink:!0}),r=b(()=>n.value.length&&o.value.label||e.value.appearance||t.value.socialLinks);return(d,p)=>r.value?(a(),k($e,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:h(()=>[i(n).length&&i(o).label?(a(),l("div",sa,[c("p",na,S(i(o).label),1),(a(!0),l(A,null,B(i(n),v=>(a(),k(oe,{key:v.link,item:v},null,8,["item"]))),128))])):f("",!0),i(e).appearance&&i(e).appearance!=="force-dark"?(a(),l("div",oa,[c("div",aa,[c("p",ra,S(i(t).darkModeSwitchLabel||"Appearance"),1),c("div",ia,[_(me)])])])):f("",!0),i(t).socialLinks?(a(),l("div",la,[c("div",ca,[_(ke,{class:"social-links-list",links:i(t).socialLinks},null,8,["links"])])])):f("",!0)]),_:1})):f("",!0)}}),da=m(ua,[["__scopeId","data-v-8e87c032"]]),va=s=>(E("data-v-6bee1efd"),s=s(),D(),s),ha=["aria-expanded"],pa=va(()=>c("span",{class:"container"},[c("span",{class:"top"}),c("span",{class:"middle"}),c("span",{class:"bottom"})],-1)),_a=[pa],fa=g({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(s){return(e,t)=>(a(),l("button",{type:"button",class:N(["VPNavBarHamburger",{active:e.active}]),"aria-label":"mobile navigation","aria-expanded":e.active,"aria-controls":"VPNavScreen",onClick:t[0]||(t[0]=n=>e.$emit("click"))},_a,10,ha))}}),ma=m(fa,[["__scopeId","data-v-6bee1efd"]]),ga=["innerHTML"],$a=g({__name:"VPNavBarMenuLink",props:{item:{}},setup(s){const{page:e}=y();return(t,n)=>(a(),k(z,{class:N({VPNavBarMenuLink:!0,active:i(x)(i(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel,tabindex:"0"},{default:h(()=>[c("span",{innerHTML:t.item.text},null,8,ga)]),_:1},8,["class","href","target","rel"]))}}),ka=m($a,[["__scopeId","data-v-cb318fec"]]),ba=g({__name:"VPNavBarMenuGroup",props:{item:{}},setup(s){const e=s,{page:t}=y(),n=r=>"link"in r?x(t.value.relativePath,r.link,!!e.item.activeMatch):r.items.some(n),o=b(()=>n(e.item));return(r,d)=>(a(),k($e,{class:N({VPNavBarMenuGroup:!0,active:i(x)(i(t).relativePath,r.item.activeMatch,!!r.item.activeMatch)||o.value}),button:r.item.text,items:r.item.items},null,8,["class","button","items"]))}}),ya=s=>(E("data-v-f732b5d0"),s=s(),D(),s),wa={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},Pa=ya(()=>c("span",{id:"main-nav-aria-label",class:"visually-hidden"},"Main Navigation",-1)),Va=g({__name:"VPNavBarMenu",setup(s){const{theme:e}=y();return(t,n)=>i(e).nav?(a(),l("nav",wa,[Pa,(a(!0),l(A,null,B(i(e).nav,o=>(a(),l(A,{key:o.text},["link"in o?(a(),k(ka,{key:0,item:o},null,8,["item"])):(a(),k(ba,{key:1,item:o},null,8,["item"]))],64))),128))])):f("",!0)}}),La=m(Va,[["__scopeId","data-v-f732b5d0"]]);function Sa(s,e){const{localeIndex:t}=y();function n(o){var V,P;const r=o.split("."),d=s&&typeof s=="object",p=d&&((P=(V=s.locales)==null?void 0:V[t.value])==null?void 0:P.translations)||null,v=d&&s.translations||null;let $=p,w=v,M=e;const C=r.pop();for(const I of r){let L=null;const q=M==null?void 0:M[I];q&&(L=M=q);const W=w==null?void 0:w[I];W&&(L=w=W);const R=$==null?void 0:$[I];R&&(L=$=R),q||(M=L),W||(w=L),R||($=L)}return($==null?void 0:$[C])??(w==null?void 0:w[C])??(M==null?void 0:M[C])??""}return n}const Ma=["aria-label"],Ca={class:"DocSearch-Button-Container"},Ia=c("svg",{class:"DocSearch-Search-Icon",width:"20",height:"20",viewBox:"0 0 20 20","aria-label":"search icon"},[c("path",{d:"M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z",stroke:"currentColor",fill:"none","fill-rule":"evenodd","stroke-linecap":"round","stroke-linejoin":"round"})],-1),Ta={class:"DocSearch-Button-Placeholder"},Aa=c("span",{class:"DocSearch-Button-Keys"},[c("kbd",{class:"DocSearch-Button-Key"}),c("kbd",{class:"DocSearch-Button-Key"},"K")],-1),ye=g({__name:"VPNavBarSearchButton",setup(s){const{theme:e}=y(),t={button:{buttonText:"Search",buttonAriaLabel:"Search"}},n=je(Sa)(qe(()=>{var o;return(o=e.value.search)==null?void 0:o.options}),t);return(o,r)=>(a(),l("button",{type:"button",class:"DocSearch DocSearch-Button","aria-label":i(n)("button.buttonAriaLabel")},[c("span",Ca,[Ia,c("span",Ta,S(i(n)("button.buttonText")),1)]),Aa],8,Ma))}}),Na={class:"VPNavBarSearch"},Ba={id:"local-search"},Ha={key:1,id:"docsearch"},za=g({__name:"VPNavBarSearch",setup(s){const e=()=>null,t=Re(()=>Ke(()=>import("./VPAlgoliaSearchBox.ELVt2j7H.js"),__vite__mapDeps([0,1]))),{theme:n}=y(),o=T(!1),r=T(!1),d=()=>{const C="VPAlgoliaPreconnect";(window.requestIdleCallback||setTimeout)(()=>{var I;const P=document.createElement("link");P.id=C,P.rel="preconnect",P.href=`https://${(((I=n.value.search)==null?void 0:I.options)??n.value.algolia).appId}-dsn.algolia.net`,P.crossOrigin="",document.head.appendChild(P)})};U(()=>{d();const C=P=>{(P.key.toLowerCase()==="k"&&(P.metaKey||P.ctrlKey)||!$(P)&&P.key==="/")&&(P.preventDefault(),p(),V())},V=()=>{window.removeEventListener("keydown",C)};window.addEventListener("keydown",C),te(V)});function p(){o.value||(o.value=!0,setTimeout(v,16))}function v(){const C=new Event("keydown");C.key="k",C.metaKey=!0,window.dispatchEvent(C),setTimeout(()=>{document.querySelector(".DocSearch-Modal")||v()},16)}function $(C){const V=C.target,P=V.tagName;return V.isContentEditable||P==="INPUT"||P==="SELECT"||P==="TEXTAREA"}const w=T(!1),M="algolia";return(C,V)=>{var P;return a(),l("div",Na,[i(M)==="local"?(a(),l(A,{key:0},[w.value?(a(),k(i(e),{key:0,onClose:V[0]||(V[0]=I=>w.value=!1)})):f("",!0),c("div",Ba,[_(ye,{onClick:V[1]||(V[1]=I=>w.value=!0)})])],64)):i(M)==="algolia"?(a(),l(A,{key:1},[o.value?(a(),k(i(t),{key:0,algolia:((P=i(n).search)==null?void 0:P.options)??i(n).algolia,onVnodeBeforeMount:V[2]||(V[2]=I=>r.value=!0)},null,8,["algolia"])):f("",!0),r.value?f("",!0):(a(),l("div",Ha,[_(ye,{onClick:p})]))],64)):f("",!0)])}}}),Ea=g({__name:"VPNavBarSocialLinks",setup(s){const{theme:e}=y();return(t,n)=>i(e).socialLinks?(a(),k(ke,{key:0,class:"VPNavBarSocialLinks",links:i(e).socialLinks},null,8,["links"])):f("",!0)}}),Da=m(Ea,[["__scopeId","data-v-ef6192dc"]]),Fa=["href"],Oa=g({__name:"VPNavBarTitle",setup(s){const{site:e,theme:t}=y(),{hasSidebar:n}=F(),{currentLang:o}=J();return(r,d)=>(a(),l("div",{class:N(["VPNavBarTitle",{"has-sidebar":i(n)}])},[c("a",{class:"title",href:i(t).logoLink??i(ve)(i(o).link)},[u(r.$slots,"nav-bar-title-before",{},void 0,!0),i(t).logo?(a(),k(Q,{key:0,class:"logo",image:i(t).logo},null,8,["image"])):f("",!0),i(t).siteTitle?(a(),l(A,{key:1},[H(S(i(t).siteTitle),1)],64)):i(t).siteTitle===void 0?(a(),l(A,{key:2},[H(S(i(e).title),1)],64)):f("",!0),u(r.$slots,"nav-bar-title-after",{},void 0,!0)],8,Fa)],2))}}),xa=m(Oa,[["__scopeId","data-v-2973dbb4"]]),Ua={},Ga={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},ja=c("path",{d:"M0 0h24v24H0z",fill:"none"},null,-1),qa=c("path",{d:" M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z ",class:"css-c4d79v"},null,-1),Ra=[ja,qa];function Ka(s,e){return a(),l("svg",Ga,Ra)}const Ne=m(Ua,[["render",Ka]]),Wa={class:"items"},Ya={class:"title"},Ja=g({__name:"VPNavBarTranslations",setup(s){const{theme:e}=y(),{localeLinks:t,currentLang:n}=J({correspondingLink:!0});return(o,r)=>i(t).length&&i(n).label?(a(),k($e,{key:0,class:"VPNavBarTranslations",icon:Ne,label:i(e).langMenuLabel||"Change language"},{default:h(()=>[c("div",Wa,[c("p",Ya,S(i(n).label),1),(a(!0),l(A,null,B(i(t),d=>(a(),k(oe,{key:d.link,item:d},null,8,["item"]))),128))])]),_:1},8,["label"])):f("",!0)}}),Xa=m(Ja,[["__scopeId","data-v-ff4524ae"]]),Za=s=>(E("data-v-5befd255"),s=s(),D(),s),Qa={class:"container"},er={class:"title"},tr={class:"content"},sr=Za(()=>c("div",{class:"curtain"},null,-1)),nr={class:"content-body"},or=g({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(s){const{y:e}=Ve(),{hasSidebar:t}=F(),{frontmatter:n}=y(),o=T({});return we(()=>{o.value={"has-sidebar":t.value,top:n.value.layout==="home"&&e.value===0}}),(r,d)=>(a(),l("div",{class:N(["VPNavBar",o.value])},[c("div",Qa,[c("div",er,[_(xa,null,{"nav-bar-title-before":h(()=>[u(r.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":h(()=>[u(r.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3})]),c("div",tr,[sr,c("div",nr,[u(r.$slots,"nav-bar-content-before",{},void 0,!0),_(za,{class:"search"}),_(La,{class:"menu"}),_(Xa,{class:"translations"}),_(go,{class:"appearance"}),_(Da,{class:"social-links"}),_(da,{class:"extra"}),u(r.$slots,"nav-bar-content-after",{},void 0,!0),_(ma,{class:"hamburger",active:r.isScreenOpen,onClick:d[0]||(d[0]=p=>r.$emit("toggle-screen"))},null,8,["active"])])])])],2))}}),ar=m(or,[["__scopeId","data-v-5befd255"]]),rr={key:0,class:"VPNavScreenAppearance"},ir={class:"text"},lr=g({__name:"VPNavScreenAppearance",setup(s){const{site:e,theme:t}=y();return(n,o)=>i(e).appearance&&i(e).appearance!=="force-dark"?(a(),l("div",rr,[c("p",ir,S(i(t).darkModeSwitchLabel||"Appearance"),1),_(me)])):f("",!0)}}),cr=m(lr,[["__scopeId","data-v-338d9b48"]]),ur=g({__name:"VPNavScreenMenuLink",props:{item:{}},setup(s){const e=ne("close-screen");return(t,n)=>(a(),k(z,{class:"VPNavScreenMenuLink",href:t.item.link,target:t.item.target,rel:t.item.rel,onClick:i(e)},{default:h(()=>[H(S(t.item.text),1)]),_:1},8,["href","target","rel","onClick"]))}}),dr=m(ur,[["__scopeId","data-v-fe523e3d"]]),vr={},hr={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},pr=c("path",{d:"M18.9,10.9h-6v-6c0-0.6-0.4-1-1-1s-1,0.4-1,1v6h-6c-0.6,0-1,0.4-1,1s0.4,1,1,1h6v6c0,0.6,0.4,1,1,1s1-0.4,1-1v-6h6c0.6,0,1-0.4,1-1S19.5,10.9,18.9,10.9z"},null,-1),_r=[pr];function fr(s,e){return a(),l("svg",hr,_r)}const mr=m(vr,[["render",fr]]),gr=g({__name:"VPNavScreenMenuGroupLink",props:{item:{}},setup(s){const e=ne("close-screen");return(t,n)=>(a(),k(z,{class:"VPNavScreenMenuGroupLink",href:t.item.link,target:t.item.target,rel:t.item.rel,onClick:i(e)},{default:h(()=>[H(S(t.item.text),1)]),_:1},8,["href","target","rel","onClick"]))}}),Be=m(gr,[["__scopeId","data-v-aea78dd1"]]),$r={class:"VPNavScreenMenuGroupSection"},kr={key:0,class:"title"},br=g({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(s){return(e,t)=>(a(),l("div",$r,[e.text?(a(),l("p",kr,S(e.text),1)):f("",!0),(a(!0),l(A,null,B(e.items,n=>(a(),k(Be,{key:n.text,item:n},null,8,["item"]))),128))]))}}),yr=m(br,[["__scopeId","data-v-f60dbfa7"]]),wr=["aria-controls","aria-expanded"],Pr=["innerHTML"],Vr=["id"],Lr={key:1,class:"group"},Sr=g({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(s){const e=s,t=T(!1),n=b(()=>`NavScreenGroup-${e.text.replace(" ","-").toLowerCase()}`);function o(){t.value=!t.value}return(r,d)=>(a(),l("div",{class:N(["VPNavScreenMenuGroup",{open:t.value}])},[c("button",{class:"button","aria-controls":n.value,"aria-expanded":t.value,onClick:o},[c("span",{class:"button-text",innerHTML:r.text},null,8,Pr),_(mr,{class:"button-icon"})],8,wr),c("div",{id:n.value,class:"items"},[(a(!0),l(A,null,B(r.items,p=>(a(),l(A,{key:p.text},["link"in p?(a(),l("div",{key:p.text,class:"item"},[_(Be,{item:p},null,8,["item"])])):(a(),l("div",Lr,[_(yr,{text:p.text,items:p.items},null,8,["text","items"])]))],64))),128))],8,Vr)],2))}}),Mr=m(Sr,[["__scopeId","data-v-32e4a89c"]]),Cr={key:0,class:"VPNavScreenMenu"},Ir=g({__name:"VPNavScreenMenu",setup(s){const{theme:e}=y();return(t,n)=>i(e).nav?(a(),l("nav",Cr,[(a(!0),l(A,null,B(i(e).nav,o=>(a(),l(A,{key:o.text},["link"in o?(a(),k(dr,{key:0,item:o},null,8,["item"])):(a(),k(Mr,{key:1,text:o.text||"",items:o.items},null,8,["text","items"]))],64))),128))])):f("",!0)}}),Tr=g({__name:"VPNavScreenSocialLinks",setup(s){const{theme:e}=y();return(t,n)=>i(e).socialLinks?(a(),k(ke,{key:0,class:"VPNavScreenSocialLinks",links:i(e).socialLinks},null,8,["links"])):f("",!0)}}),Ar={class:"list"},Nr=g({__name:"VPNavScreenTranslations",setup(s){const{localeLinks:e,currentLang:t}=J({correspondingLink:!0}),n=T(!1);function o(){n.value=!n.value}return(r,d)=>i(e).length&&i(t).label?(a(),l("div",{key:0,class:N(["VPNavScreenTranslations",{open:n.value}])},[c("button",{class:"title",onClick:o},[_(Ne,{class:"icon lang"}),H(" "+S(i(t).label)+" ",1),_(Ae,{class:"icon chevron"})]),c("ul",Ar,[(a(!0),l(A,null,B(i(e),p=>(a(),l("li",{key:p.link,class:"item"},[_(z,{class:"link",href:p.link},{default:h(()=>[H(S(p.text),1)]),_:2},1032,["href"])]))),128))])],2)):f("",!0)}}),Br=m(Nr,[["__scopeId","data-v-41505286"]]),Hr={class:"container"},zr=g({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(s){const e=T(null),t=Le(K?document.body:null);return(n,o)=>(a(),k(ce,{name:"fade",onEnter:o[0]||(o[0]=r=>t.value=!0),onAfterLeave:o[1]||(o[1]=r=>t.value=!1)},{default:h(()=>[n.open?(a(),l("div",{key:0,class:"VPNavScreen",ref_key:"screen",ref:e,id:"VPNavScreen"},[c("div",Hr,[u(n.$slots,"nav-screen-content-before",{},void 0,!0),_(Ir,{class:"menu"}),_(Br,{class:"translations"}),_(cr,{class:"appearance"}),_(Tr,{class:"social-links"}),u(n.$slots,"nav-screen-content-after",{},void 0,!0)])],512)):f("",!0)]),_:3}))}}),Er=m(zr,[["__scopeId","data-v-57cce842"]]),Dr={key:0,class:"VPNav"},Fr=g({__name:"VPNav",setup(s){const{isScreenOpen:e,closeScreen:t,toggleScreen:n}=Yn(),{frontmatter:o}=y(),r=b(()=>o.value.navbar!==!1);return Se("close-screen",t),ee(()=>{K&&document.documentElement.classList.toggle("hide-nav",!r.value)}),(d,p)=>r.value?(a(),l("header",Dr,[_(ar,{"is-screen-open":i(e),onToggleScreen:i(n)},{"nav-bar-title-before":h(()=>[u(d.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":h(()=>[u(d.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":h(()=>[u(d.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":h(()=>[u(d.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),_(Er,{open:i(e)},{"nav-screen-content-before":h(()=>[u(d.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":h(()=>[u(d.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])])):f("",!0)}}),Or=m(Fr,[["__scopeId","data-v-7ad780c2"]]),xr=s=>(E("data-v-bd01e0d5"),s=s(),D(),s),Ur=["role","tabindex"],Gr=xr(()=>c("div",{class:"indicator"},null,-1)),jr={key:1,class:"items"},qr=g({__name:"VPSidebarItem",props:{item:{},depth:{}},setup(s){const e=s,{collapsed:t,collapsible:n,isLink:o,isActiveLink:r,hasActiveLink:d,hasChildren:p,toggle:v}=gt(b(()=>e.item)),$=b(()=>p.value?"section":"div"),w=b(()=>o.value?"a":"div"),M=b(()=>p.value?e.depth+2===7?"p":`h${e.depth+2}`:"p"),C=b(()=>o.value?void 0:"button"),V=b(()=>[[`level-${e.depth}`],{collapsible:n.value},{collapsed:t.value},{"is-link":o.value},{"is-active":r.value},{"has-active":d.value}]);function P(L){"key"in L&&L.key!=="Enter"||!e.item.link&&v()}function I(){e.item.link&&v()}return(L,q)=>{const W=j("VPSidebarItem",!0);return a(),k(O($.value),{class:N(["VPSidebarItem",V.value])},{default:h(()=>[L.item.text?(a(),l("div",X({key:0,class:"item",role:C.value},We(L.item.items?{click:P,keydown:P}:{},!0),{tabindex:L.item.items&&0}),[Gr,L.item.link?(a(),k(z,{key:0,tag:w.value,class:"link",href:L.item.link,rel:L.item.rel,target:L.item.target},{default:h(()=>[(a(),k(O(M.value),{class:"text",innerHTML:L.item.text},null,8,["innerHTML"]))]),_:1},8,["tag","href","rel","target"])):(a(),k(O(M.value),{key:1,class:"text",innerHTML:L.item.text},null,8,["innerHTML"])),L.item.collapsed!=null?(a(),l("div",{key:2,class:"caret",role:"button","aria-label":"toggle section",onClick:I,onKeydown:Ye(I,["enter"]),tabindex:"0"},[_(fe,{class:"caret-icon"})],32)):f("",!0)],16,Ur)):f("",!0),L.item.items&&L.item.items.length?(a(),l("div",jr,[L.depth<5?(a(!0),l(A,{key:0},B(L.item.items,R=>(a(),k(W,{key:R.text,item:R,depth:L.depth+1},null,8,["item","depth"]))),128)):f("",!0)])):f("",!0)]),_:1},8,["class"])}}}),Rr=m(qr,[["__scopeId","data-v-bd01e0d5"]]),He=s=>(E("data-v-168699b1"),s=s(),D(),s),Kr=He(()=>c("div",{class:"curtain"},null,-1)),Wr={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},Yr=He(()=>c("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),Jr=g({__name:"VPSidebar",props:{open:{type:Boolean}},setup(s){const{sidebarGroups:e,hasSidebar:t}=F(),n=s,o=T(null),r=Le(K?document.body:null);return G([n,o],()=>{var d;n.open?(r.value=!0,(d=o.value)==null||d.focus()):r.value=!1},{immediate:!0,flush:"post"}),(d,p)=>i(t)?(a(),l("aside",{key:0,class:N(["VPSidebar",{open:d.open}]),ref_key:"navEl",ref:o,onClick:p[0]||(p[0]=Je(()=>{},["stop"]))},[Kr,c("nav",Wr,[Yr,u(d.$slots,"sidebar-nav-before",{},void 0,!0),(a(!0),l(A,null,B(i(e),v=>(a(),l("div",{key:v.text,class:"group"},[_(Rr,{item:v,depth:0},null,8,["item"])]))),128)),u(d.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):f("",!0)}}),Xr=m(Jr,[["__scopeId","data-v-168699b1"]]),Zr=g({__name:"VPSkipLink",setup(s){const e=se(),t=T();G(()=>e.path,()=>t.value.focus());function n({target:o}){const r=document.getElementById(decodeURIComponent(o.hash).slice(1));if(r){const d=()=>{r.removeAttribute("tabindex"),r.removeEventListener("blur",d)};r.setAttribute("tabindex","-1"),r.addEventListener("blur",d),r.focus(),window.scrollTo(0,0)}}return(o,r)=>(a(),l(A,null,[c("span",{ref_key:"backToTop",ref:t,tabindex:"-1"},null,512),c("a",{href:"#VPContent",class:"VPSkipLink visually-hidden",onClick:n}," Skip to content ")],64))}}),Qr=m(Zr,[["__scopeId","data-v-c8291ffa"]]),ei=g({__name:"Layout",setup(s){const{isOpen:e,open:t,close:n}=F(),o=se();G(()=>o.path,n),mt(e,n);const{frontmatter:r}=y(),d=Xe(),p=b(()=>!!d["home-hero-image"]);return Se("hero-image-slot-exists",p),(v,$)=>{const w=j("Content");return i(r).layout!==!1?(a(),l("div",{key:0,class:N(["Layout",i(r).pageClass])},[u(v.$slots,"layout-top",{},void 0,!0),_(Qr),_(st,{class:"backdrop",show:i(e),onClick:i(n)},null,8,["show","onClick"]),_(Or,null,{"nav-bar-title-before":h(()=>[u(v.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":h(()=>[u(v.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":h(()=>[u(v.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":h(()=>[u(v.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":h(()=>[u(v.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":h(()=>[u(v.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3}),_(Wn,{open:i(e),onOpenMenu:i(t)},null,8,["open","onOpenMenu"]),_(Xr,{open:i(e)},{"sidebar-nav-before":h(()=>[u(v.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":h(()=>[u(v.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),_(Ln,null,{"page-top":h(()=>[u(v.$slots,"page-top",{},void 0,!0)]),"page-bottom":h(()=>[u(v.$slots,"page-bottom",{},void 0,!0)]),"not-found":h(()=>[u(v.$slots,"not-found",{},void 0,!0)]),"home-hero-before":h(()=>[u(v.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info":h(()=>[u(v.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-image":h(()=>[u(v.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":h(()=>[u(v.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":h(()=>[u(v.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":h(()=>[u(v.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":h(()=>[u(v.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":h(()=>[u(v.$slots,"doc-before",{},void 0,!0)]),"doc-after":h(()=>[u(v.$slots,"doc-after",{},void 0,!0)]),"doc-top":h(()=>[u(v.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":h(()=>[u(v.$slots,"doc-bottom",{},void 0,!0)]),"aside-top":h(()=>[u(v.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":h(()=>[u(v.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":h(()=>[u(v.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":h(()=>[u(v.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":h(()=>[u(v.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":h(()=>[u(v.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),_(Tn),u(v.$slots,"layout-bottom",{},void 0,!0)],2)):(a(),k(w,{key:1}))}}}),ti=m(ei,[["__scopeId","data-v-9d8abc1e"]]),ni={Layout:ti,enhanceApp:({app:s})=>{s.component("Badge",Qe)}};export{ni as t,y as u}; +function __vite__mapDeps(indexes) { + if (!__vite__mapDeps.viteFileDeps) { + __vite__mapDeps.viteFileDeps = ["assets/chunks/VPAlgoliaSearchBox.ELVt2j7H.js","assets/chunks/framework.g9eZ-ZSs.js"] + } + return indexes.map((i) => __vite__mapDeps.viteFileDeps[i]) +} \ No newline at end of file diff --git a/assets/concepts_include-tree.md.tAg1rwYk.js b/assets/concepts_include-tree.md.tAg1rwYk.js new file mode 100644 index 000000000..3fff76cf5 --- /dev/null +++ b/assets/concepts_include-tree.md.tAg1rwYk.js @@ -0,0 +1,111 @@ +import{_ as l,D as p,o as t,c,I as r,w as a,R as n,k as s,a as e}from"./chunks/framework.g9eZ-ZSs.js";const I=JSON.parse('{"title":"Include Tree","description":"","frontmatter":{},"headers":[],"relativePath":"concepts/include-tree.md","filePath":"concepts/include-tree.md"}'),D={name:"concepts/include-tree.md"},y=n(`

Include Tree

When Coalesce maps from the your POCO objects that are returned from EF Core queries, it will follow a structure called an IncludeTree to determine what relationships to follow and how deep to go in re-creating that structure in the mapped DTOs.

Purpose

Without an IncludeTree present, Coalesce will map the entire object graph that is reachable from the root object. This can often spiral out of control if there aren't any rules defining how far to go while turning this graph into a tree.

For example, suppose you had the following model with a many-to-many relationship (key properties omitted for brevity):

c#
public class Employee
+{
+    [ManyToMany("Projects")]
+    public ICollection<EmployeeProject> EmployeeProjects { get; set; }
+            
+    public static IQueryable<Employee> WithProjectsAndMembers(AppDbContext db, ClaimsPrincipal user)
+    {
+        // Load all projects of an employee, as well as all members of those projects.
+        return db.Employees
+            .Include(e => e.EmployeeProjects)
+                .ThenInclude(ep => ep.Project.EmployeeProjects)
+                .ThenInclude(ep => ep.Employee);
+    }
+}
+
+public class Project
+{
+    [ManyToMany("Employees")]
+    public ICollection<EmployeeProject> EmployeeProjects { get; set; }
+}
+
+public class EmployeeProject
+{
+    public Employee Employee { get; set; }
+    public Project Project { get; set; }
+}

Now, imagine that you have five employees and five projects, with every employee being a member of every project (i.e. there are 25 EmployeeProject rows).

Your client code makes a call to the Coalesce-generated API to load Employee #1 using the custom data source:

`,8),i=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"Employee"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"EmployeeViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," employee"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," EmployeeViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," Employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"DataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"WithProjectsAndMembers"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")])])])],-1),d=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," employee"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Employee"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"WithProjectsAndMembers"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")])])])],-1),C=n(`

If you're already familiar with the fact that an IncludeTree is implicitly created in this scenario, then imagine for a moment that this is not the case (if you're not familiar with this fact, then keep reading!).

After Coalesce has called your Data Sources and evaluated the EF IQueryable returned, there are now 35 objects loaded into the current DbContext being used to handle this request - the 5 employees, 5 projects, and 25 relationships.

To map these objects to DTOs, we start with the root (employee #1) and expand outward from there until the entire object graph has been faithfully re-created with DTO objects, including all navigation properties.

The root DTO object (employee #1) then eventually is passed to the JSON serializer by ASP.NET Core to formulate the response to the request. As the object is serialized to JSON, the only objects that are not serialized are those that were already serialized as an ancestor of itself. What this ultimately means is that the structure of the serialized JSON with our example scenario ends up following a pattern like this (the vast majority of items have been omitted):

Employee#1
+    EmployeeProject#1
+        Project#1
+            EmployeeProject#6
+                Employee#2
+                    EmployeeProject#7
+                        Project#2
+                            ... continues down through all remaining employees and projects.
+                    ...
+            EmployeeProject#11
+                Employee#3
+            ...
+    EmployeeProject#2
+        Project#2
+    ...

See how the structure includes the EmployeeProjects of Employee#2? We didn't write our custom data source calls to .Include in such a way that indicated that we wanted the root employee, their projects, the employees of those projects, and then the projects of those employees. But, because the JSON serializer blindly follows the object graph, that's what gets serialized. It turns out that the depth of the tree increases on the order of O(n^2), and the total size increases on the order of Ω(n!).

This is where IncludeTree comes in. When you use a custom data source like we did above, Coalesce automatically captures the structure of the calls to .Include and .ThenInclude, and uses this to perform trimming during creation of the DTO objects.

With an IncludeTree in place, our new serialized structure looks like this:

Employee#1
+    EmployeeProject#1
+        Project#1
+            EmployeeProject#6
+                Employee#2
+            EmployeeProject#11
+                Employee#3
+            ...
+    EmployeeProject#2
+        Project#2
+    ...

No more extra data trailing off the end of the projects' employees!

Usage

Custom Data Sources

In most cases, you don't have to worry about creating an IncludeTree. When using the Standard Data Source (or a derivative), the structure of the .Include and .ThenInclude calls will be captured automatically and be turned into an IncludeTree.

However, there are sometimes cases where you perform complex loading in these methods that involves loading data into the current DbContext outside of the IQueryable that is returned from the method. The most common situation for this is needing to conditionally load related data - for example, load all children of an object where the child has a certain value of a Status property.

In these cases, Coalesce provides a pair of extension methods, .IncludedSeparately and .ThenIncluded, that can be used to merge in the structure of the data that was loaded separately from the main IQueryable.

For example:

c#
public override IQueryable<Employee> GetQuery()
+{
+    // Load all projects that are complete, and their members, into the db context.
+    Db.Projects
+        .Include(p => p.EmployeeProjects).ThenInclude(ep => ep.Employee)
+        .Where(p => p.Status == ProjectStatus.Complete)
+        .Load();
+
+    // Return an employee query, and notify Coalesce that we loaded the projects in a different query.
+    return Db.Employees
+        .IncludedSeparately(e => e.EmployeeProjects)
+        .ThenIncluded(ep => ep.Project.EmployeeProjects)
+        .ThenIncluded(ep => ep.Employee);
+}

You can also override the GetIncludeTree method of the Standard Data Source to achieve the same result:

c#
public override IncludeTree GetIncludeTree(IQueryable<T> query, IDataSourceParameters parameters) => Db
+    .Employees
+    .IncludedSeparately(e => e.EmployeeProjects)
+    .ThenIncluded(ep => ep.Project.EmployeeProjects)
+    .ThenIncluded(ep => ep.Employee)
+    .GetIncludeTree();

Model Methods

If you have custom methods that return object data, you may also want to control the structure of the returned data when it is serialized. Fortunately, you can also use IncludeTree in these situations. Without an IncludeTree, the entire object graph is traversed and serialized without limit.

TIP

An IncludeTree can be obtained from any IQueryable by calling the GetIncludeTree extension method (using IntelliTect.Coalesce.Helpers.IncludeTree).

In situations where your root object isn't on your DbContext (see External Types), you can use Enumerable.Empty<MyNonDbClass>().AsQueryable() to get an IQueryable to start from. When you do this, you must use IncludedSeparately - the regular EF Include method won't work without a DbSet.

See the following two techniques for returning an IncludeTree from a custom method:

ItemResult.IncludeTree

The easiest and most versatile way to return an IncludeTree from a custom method is to make that method return an ItemResult<T>, and then set the IncludeTree property of the ItemResult object. For example:

c#
public class Employee
+{
+    public async Task<ItemResult<ICollection<Employee>>> GetChainOfCommand(AppDbContext db)
+    {
+        IQueryable<Employee> query = db.Employees
+            .Include(e => e.Supervisor);
+
+        var ret = new List<Employee>();
+        var current = this;
+        while (current.Supervisor != null)
+        {
+            ret.Push(current);
+            current = await query.FirstOrDefaultAsync(e => e.EmployeeId == current.SupervisorId);
+        }
+
+        return new(ret, includeTree: query.GetIncludeTree());
+    }
+}

Out Parameter

To tell Coalesce about the structure of the data returned from a model method, you can also add out IncludeTree includeTree to the signature of the method. Inside your method, set includeTree to an instance of an IncludeTree. However, this approach cannot be used on async methods, since out parameters are not allowed on async methods in C#. For example:

c#
public class Employee
+{
+    public ICollection<Employee> GetChainOfCommand(AppDbContext db, out IncludeTree includeTree)
+    {
+        IQueryable<Employee> query = db.Employees
+            .Include(e => e.Supervisor);
+
+        var ret = new List<Employee>();
+        var current = this;
+        while (current.Supervisor != null)
+        {
+            ret.Push(current);
+            current = query.FirstOrDefault(e => e.EmployeeId == current.SupervisorId);
+        }
+
+        includeTree = query.GetIncludeTree();
+
+        return ret;
+    }
+}

External Type Caveats

One important point remains regarding IncludeTree - it is not used to control the serialization of objects which are not mapped to the database, known as External Types. External Types are always put into the DTOs when encountered (unless otherwise prevented by [DtoIncludes] & [DtoExcludes] or Security Attributes), with the assumption that because these objects are created by you (as opposed to Entity Framework), you are responsible for preventing any undesired circular references.

By not filtering unmapped properties, you as the developer don't need to account for them in every place throughout your application where they appear - instead, they 'just work' and show up on the client as expected.

Note also that this statement does not apply to database-mapped objects that hang off of unmapped objects - any time a database-mapped object appears, it will be controlled by your include tree. If no include tree is present (because nothing was specified for the unmapped property), these mapped objects hanging off of unmapped objects will be serialized freely and with all circular references, unless you include some calls to .IncludedSeparately(m => m.MyUnmappedProperty.MyMappedProperty) to limit those objects down.

`,33);function u(h,m,E,b,g,F){const o=p("CodeTabs");return t(),c("div",null,[y,r(o,null,{vue:a(()=>[i]),knockout:a(()=>[d]),_:1}),C])}const A=l(D,[["render",u]]);export{I as __pageData,A as default}; diff --git a/assets/concepts_include-tree.md.tAg1rwYk.lean.js b/assets/concepts_include-tree.md.tAg1rwYk.lean.js new file mode 100644 index 000000000..534d9d511 --- /dev/null +++ b/assets/concepts_include-tree.md.tAg1rwYk.lean.js @@ -0,0 +1,8 @@ +import{_ as l,D as p,o as t,c,I as r,w as a,R as n,k as s,a as e}from"./chunks/framework.g9eZ-ZSs.js";const I=JSON.parse('{"title":"Include Tree","description":"","frontmatter":{},"headers":[],"relativePath":"concepts/include-tree.md","filePath":"concepts/include-tree.md"}'),D={name:"concepts/include-tree.md"},y=n("",8),i=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"Employee"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"EmployeeViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," employee"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," EmployeeViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," Employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"DataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"WithProjectsAndMembers"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")])])])],-1),d=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," employee"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Employee"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"WithProjectsAndMembers"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"employee"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")])])])],-1),C=n("",33);function u(h,m,E,b,g,F){const o=p("CodeTabs");return t(),c("div",null,[y,r(o,null,{vue:a(()=>[i]),knockout:a(()=>[d]),_:1}),C])}const A=l(D,[["render",u]]);export{I as __pageData,A as default}; diff --git a/assets/concepts_includes.md.va6Oh-E9.js b/assets/concepts_includes.md.va6Oh-E9.js new file mode 100644 index 000000000..8e776a99e --- /dev/null +++ b/assets/concepts_includes.md.va6Oh-E9.js @@ -0,0 +1,50 @@ +import{_ as p,D as a,o as r,c as i,I as l,w as o,a as e,k as s,R as t}from"./chunks/framework.g9eZ-ZSs.js";const V=JSON.parse('{"title":"Includes String","description":"","frontmatter":{},"headers":[],"relativePath":"concepts/includes.md","filePath":"concepts/includes.md"}'),D={name:"concepts/includes.md"},d=s("h1",{id:"includes-string",tabindex:"-1"},[e("Includes String "),s("a",{class:"header-anchor",href:"#includes-string","aria-label":'Permalink to "Includes String"'},"​")],-1),y=s("p",null,'Coalesce provides a number of extension points for loading & serialization which make use of a concept called an "includes string" (also referred to as "include string" or just "includes").',-1),C=s("h2",{id:"includes-string-1",tabindex:"-1"},[e("Includes String "),s("a",{class:"header-anchor",href:"#includes-string-1","aria-label":'Permalink to "Includes String"'},"​")],-1),u=s("p",null,"The includes string is simply a string which can be set to any arbitrary value. It is passed from the client to the server in order to customize data loading and serialization. It can be set on both the TypeScript ViewModels and the ListViewModels.",-1),h=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},", "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," person"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")])])])],-1),m=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," person"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Person"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")])])])],-1),b=t(`

The default value (i.e. no action) is the empty string.

Special Values

There are a few values of includes that are either set by default in the auto-generated views, or otherwise have special meaning:

ValueDescription
'none'Setting includes to none suppresses the Default Loading Behavior provided by the Standard Data Source - The resulting data will be the requested object (or list of objects) and nothing more.
'admin-list'Used when loading a list of objects in the Vue admin list page.
'admin-editor'Used when loading an object in the Vue admin editor component.
'Editor'Used when loading an object in the generated Knockout CreateEdit views.
'<ModelName>ListGen'Used when loading a list of objects in the generated Knockout Table and Cards views. For example, PersonListGen

DtoIncludes & DtoExcludes

Main document: [DtoIncludes] & [DtoExcludes].

There are two C# attributes, DtoIncludes and DtoExcludes, that can be used to annotate your data model in order to customize what data gets put into the DTOs and ultimately serialized to JSON and sent out to the client.

When the database entries are returned to the client they will be trimmed based on the requested includes string and the values in DtoExcludes and DtoIncludes.

DANGER

These attributes are not security attributes - consumers of your application's API can set the includes string to any value when making a request.

Do not use them to keep certain data private - use the Security Attributes family of attributes for that.

It is important to note that the value of the includes string will match against these attributes on any of your models that appears in the object graph being mapped to DTOs - it is not limited only to the model type of the root object.

Important

DtoIncludes does not ensure that specific data will be loaded from the database. It only permits what is already loaded into the current EF DbContext to be returned from the API. See Data Sources to learn how to control what data gets loaded from the database.

Example Usage

Server code:

c#
public class Person
+{
+    // Don't include CreatedBy when editing - will be included for all other views
+    [DtoExcludes("Editor")]
+    public AppUser CreatedBy { get; set; }
+
+    // Only include the Person's Department when \`includes == "details"\` on the TypeScript ViewModel.
+    [DtoIncludes("details")]
+    public Department Department { get; set; }
+
+    // LastName will be included in all views
+    public string LastName { get; set; }
+}
+
+public class Department
+{
+    [DtoIncludes("details")]
+    public ICollection<Person> People { get; set; }
+}

Client code:

`,15),g=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList.$items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList2.items will be allowed to contain both CreatedBy and Department objects. ")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Department will be allowed to include its other Person objects.")])])])],-1),E=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}}," // objects in personList.items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}}," // objects in personList2.items will be allowed to contain both CreatedBy and Department objects. Department will be allowed to include its other Person objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")])])])],-1),w=s("h3",{id:"properties",tabindex:"-1"},[e("Properties "),s("a",{class:"header-anchor",href:"#properties","aria-label":'Permalink to "Properties"'},"​")],-1),_=t('

A comma-delimited list of values of includes on which to operate.

For DtoIncludes, this will be the values of includes for which this property will be allowed to be serialized and sent to the client.

For DtoExcludes, this will be the values of includes for which this property will not be serialized and sent to the client.

',3);function f(v,F,A,L,P,k){const n=a("CodeTabs"),c=a("Prop");return r(),i("div",null,[d,y,C,u,l(n,null,{vue:o(()=>[h]),knockout:o(()=>[m]),_:1}),b,l(n,null,{vue:o(()=>[g]),knockout:o(()=>[E]),_:1}),w,l(c,{def:"public string ContentViews { get; set; }",ctor:"1"}),e(),_])}const I=p(D,[["render",f]]);export{V as __pageData,I as default}; diff --git a/assets/concepts_includes.md.va6Oh-E9.lean.js b/assets/concepts_includes.md.va6Oh-E9.lean.js new file mode 100644 index 000000000..9ef81d721 --- /dev/null +++ b/assets/concepts_includes.md.va6Oh-E9.lean.js @@ -0,0 +1,32 @@ +import{_ as p,D as a,o as r,c as i,I as l,w as o,a as e,k as s,R as t}from"./chunks/framework.g9eZ-ZSs.js";const V=JSON.parse('{"title":"Includes String","description":"","frontmatter":{},"headers":[],"relativePath":"concepts/includes.md","filePath":"concepts/includes.md"}'),D={name:"concepts/includes.md"},d=s("h1",{id:"includes-string",tabindex:"-1"},[e("Includes String "),s("a",{class:"header-anchor",href:"#includes-string","aria-label":'Permalink to "Includes String"'},"​")],-1),y=s("p",null,'Coalesce provides a number of extension points for loading & serialization which make use of a concept called an "includes string" (also referred to as "include string" or just "includes").',-1),C=s("h2",{id:"includes-string-1",tabindex:"-1"},[e("Includes String "),s("a",{class:"header-anchor",href:"#includes-string-1","aria-label":'Permalink to "Includes String"'},"​")],-1),u=s("p",null,"The includes string is simply a string which can be set to any arbitrary value. It is passed from the client to the server in order to customize data loading and serialization. It can be set on both the TypeScript ViewModels and the ListViewModels.",-1),h=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},", "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," person"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")])])])],-1),m=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," person"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Person"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")])])])],-1),b=t("",15),g=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList.$items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList2.items will be allowed to contain both CreatedBy and Department objects. ")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Department will be allowed to include its other Person objects.")])])])],-1),E=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}}," // objects in personList.items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}}," // objects in personList2.items will be allowed to contain both CreatedBy and Department objects. Department will be allowed to include its other Person objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")])])])],-1),w=s("h3",{id:"properties",tabindex:"-1"},[e("Properties "),s("a",{class:"header-anchor",href:"#properties","aria-label":'Permalink to "Properties"'},"​")],-1),_=t("",3);function f(v,F,A,L,P,k){const n=a("CodeTabs"),c=a("Prop");return r(),i("div",null,[d,y,C,u,l(n,null,{vue:o(()=>[h]),knockout:o(()=>[m]),_:1}),b,l(n,null,{vue:o(()=>[g]),knockout:o(()=>[E]),_:1}),w,l(c,{def:"public string ContentViews { get; set; }",ctor:"1"}),e(),_])}const I=p(D,[["render",f]]);export{V as __pageData,I as default}; diff --git a/assets/history.md.ux281dRc.js b/assets/history.md.ux281dRc.js new file mode 100644 index 000000000..be8aae7e6 --- /dev/null +++ b/assets/history.md.ux281dRc.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,R as i}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse(`{"title":"Welcome to Coalesce's documentation!","description":"","frontmatter":{},"headers":[],"relativePath":"history.md","filePath":"history.md"}`),o={name:"history.md"},s=i('

Welcome to Coalesce's documentation!

Coalesce is a framework based on ASP.NET Core that makes rapidly building awesome websites much easier. A project that would take a 3 months to complete now takes 1 month. We built this because we got tired of writing all the boiler plate code that is necessary to make amazing sites.

It does this by allowing developers to focus on the creative aspects of the solution. The more mundane parts are generated automatically. This means that you get to focus on data modeling, business logic and front-end development. Coalesce does the plumbing.

Here is a typical workflow

  1. Build an EF Core data model with business logic
  2. Coalesce generates controllers, TypeScript view models, API and view model documentation, and admin pages/examples
  3. Build an interactive and intuitive user experience
  4. Rinse and repeat

Core Features

  • Built on the latest Microsoft ASP.NET Core
  • Easy to learn
  • TypeScript from the ground up
  • Flexibility to use MVC patterns as required
  • Admin pages for all your models are build automatically and include features like searching, sorting, and paging
  • Robust documentation for the framework
  • Automatically generated documentation for the API layer and TypeScript view models
  • Feature rich TypeScript view models that can be easily extended
  • Many extension points for customizations
  • Abstraction that doesn't require you know how everything works
  • Security and data trimming by role is built in
  • Flexibility about which data to return to the client
  • Open source

Is Coalesce for Everyone and Every Project?

Coalesce was designed to create line-of-business applications. It provides a more customizable and maintainable alternative to off the shelf customizable products like SharePoint and Sales Force.

You should consider using Coalesce if your project:

  • Is small to medium size (1-200 classes)
  • Requires an interactive user experience
  • Has data entry requirements, especially forms, tables, etc.
  • Needs to get started quickly with functional prototypes that can become production software

Design Decisions and Limitations

Coalesce is specifically designed to meet the needs of web developers. However, there are lots of ways to do this. We have made a set of decisions which we believe makes for a great development experience

  • ASP.NET Core: there is no intent to back port this to an earlier version
  • EF Core for the object relational mapper
  • Currently uses the full framework because .NET Core doesn't supported the required functionality, yet
  • Knockout for client-side data binding
  • Business logic most easily lives in the model classes
  • Coalesce is designed for relational databases. This might change in the future, but not until we have a compelling use case.

How Does it Work?

After you create your classes and the EF data context, Coalesce uses this information to generate code. When the Coalesce CLI (command line interface) is run, the following things happen:

  1. The model is validated to ensure that all the Coalesce specific requirements are met. This includes things like ensuring that all classes have a primary key assigned, validating that linked child objects have a key to their parent, etc. If issues are found, generation stops and the errors are displayed with advise to fix the issues.
  2. The core files needed for Coalesce are copied to the target project. This includes TypeScript base classes, customizable templates, and other files for extension points. Each file is copied twice, once as a file that can be modified in the project and once as an original file. This ensures that if any changes are made by the user these files Coalesce will not overwrite the your changes.
  3. The API controllers are generated. One is generated for each object. This includes methods that get a list of items, get a specific item, save an item, etc.
  4. The TypeScript view models are created. There are two view models for each object. One is a list view model which allows for getting an displaying lists of a type of object. This includes full functionality to sort, filter, search, page, etc. Additionally, a view model that represents the individual object is also created. This has all the properties and methods of the server side object. This is basically a client-side proxy object for representing and manipulating the object on the server side. These objects seamlessly use the API controllers to interact with the server.
  5. Next, the View controller are created. One is create for each model class and provide a tabular view, a card view (for mobile), an editor, and documentation.
  6. Finally, the CSHTML views for the controller are created. These are the actual CSHTML for the above controllers. These not only provide administrative view and editing features, but also serve as an example of how to use the framework

General Guidance

Here are a few things we have found helpful when using Coalesce

  • Learn and embrace the Coalesce paradigm and work with it rather than trying to do things another way.
  • Following what we refer to as the 'well worn path' is very helpful. Try to stick to standard ways to do things rather than trying to use esoteric features.
  • Keep your models as consistent and straightforward as possible. Use relational modeling best practices.
  • Remember that public methods on your class models are added to the client side view models and this makes calling business logic from the client really easy.
  • Don't be afraid to fall back to building parts of your site using traditional methods. Coalesce isn't right for everything. But, honestly, we have only done this a few times, like 3.

The Story

Why Coalesce

In 2014 several developers from IntelliTect got together to talk about our craft. There were lots of different backgrounds, but recently we had all been writing web code in C#. We discussed things we enjoyed and things we dreaded. There was an underlying commitment to providing customers with great sites at a reasonable cost. However, those things often seemed at odds because of the complexity of web development.

The Problem

For example, writing AJAX drop down lists with type ahead takes quite a bit of plumbing. Layer onto this the need for view models that allow for validation and saving as the user moves from field to field. We absolutely want want to deliver visually pleasing sites with complex UI paradigms. However, all this excellence adds up: complex view models, complex APIs, data binding, ugh.

Then there is that sinking feeling when you have to add another class to the project knowing that you are going to need to create all this yet again and you consider taking short cuts. Will there really be more than about 20 items in this table, maybe we don't need paging. Inevitably, the customer asks for admin screens. We consider giving them SQL Server Management Console and then consider using the built in ASP.NET list and editor pages. Better sense wins out and we end up spending two weeks building slick admin pages with paging, searching, sorting, etc.

The Path to the Solution

That evening we starting talking about the things we loved to do:

  • Data modeling
  • Figuring out and writing business logic
  • Working with customers
  • Making cool user interfaces
  • Creating something new and awesome

We also lists things that we didn't enjoy

  • Writing the same controller again
  • Creating a view model for a class that is similar but different from another one in the project
  • Putting sorting and paging on every admin page
  • Basically doing anything that feels repetitive or boilerplate

Over the next few months we talked about this issue, but couldn't find the right abstraction. We talked about other solutions that solve parts of the problem and considered putting together something from several pieces. Nothing felt unified and we ended up with leaky abstractions. We needed some way to divide the problem so that we could build the fun stuff and have something generate the boring stuff. This solution needed to be robust enough to satisfy our customer's needs and also be of use to developers without their needing to know the inner workings of the system.

Our Solution

What if we could build the models and business logic and have a tool build everything except the UI? There are great tools like Entity Framework for modeling and good tooling for minimizing duplicate code in user interfaces. And so Coalesce was born, a tool that would bring together the backend and front end development. '

Coalesce takes Entity Framework Core models and builds controllers, TypeScript view models, and admin pages automatically. These are built in a general way so that they can be applied to many different scenarios. There will always be pages that need to be written by hand and we intentionally don't support many edge cases in order to keep things simple. There is nothing wrong with building something by hand.

How has it Worked?

We have been using Coalesce for many of our web projects with great success. Typically, a project is taking about 1/3 the time it was taking before once developers ramp up. The ramp up on Coalesce has typically been a couple of days. We realized that in order for Coalesce to be useful it need to be intuitive to use and easy to understand. We have intentionally used simple paradigms to minimize the learning curve. There are complex bits, but hopefully, those are well hidden and documented as needed.

',37),n=[s];function l(r,d,h,c,u,m){return t(),a("div",null,n)}const f=e(o,[["render",l]]);export{g as __pageData,f as default}; diff --git a/assets/history.md.ux281dRc.lean.js b/assets/history.md.ux281dRc.lean.js new file mode 100644 index 000000000..6f20c4880 --- /dev/null +++ b/assets/history.md.ux281dRc.lean.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,R as i}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse(`{"title":"Welcome to Coalesce's documentation!","description":"","frontmatter":{},"headers":[],"relativePath":"history.md","filePath":"history.md"}`),o={name:"history.md"},s=i("",37),n=[s];function l(r,d,h,c,u,m){return t(),a("div",null,n)}const f=e(o,[["render",l]]);export{g as __pageData,f as default}; diff --git a/assets/index.md.J5qzQ5SM.js b/assets/index.md.J5qzQ5SM.js new file mode 100644 index 000000000..a34f8d03a --- /dev/null +++ b/assets/index.md.J5qzQ5SM.js @@ -0,0 +1 @@ +import{_ as t,D as o,o as a,c as r,I as n}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse(`{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"Coalesce","text":"Accelerated Web App Development","tagline":"ASP.NET Core • EF Core • Vue.js • TypeScript","image":{"src":"/coalesce-icon-color.svg","alt":"Coalesce"},"actions":[{"theme":"brand","text":"Introduction","link":"/introduction"},{"theme":"alt","text":"Get Started","link":"/stacks/vue/getting-started"}]},"features":[{"title":"🖨 Code Generated","details":"Design your data model and build awesome pages. Coalesce generates the boring parts in the middle."},{"title":"🧩 Extensible","details":"All functionality in Coalesce is configurable or overridable. You'll never be boxed in or get stuck."},{"title":"🔒 Secure","details":"Customization of table-level, row-level, and property-level security are all built-in. Read More."}]},"headers":[],"relativePath":"index.md","filePath":"index.md"}`),i={name:"index.md"};function l(s,c,d,p,m,u){const e=o("SiteFooter");return a(),r("div",null,[n(e)])}const g=t(i,[["render",l]]);export{_ as __pageData,g as default}; diff --git a/assets/index.md.J5qzQ5SM.lean.js b/assets/index.md.J5qzQ5SM.lean.js new file mode 100644 index 000000000..a34f8d03a --- /dev/null +++ b/assets/index.md.J5qzQ5SM.lean.js @@ -0,0 +1 @@ +import{_ as t,D as o,o as a,c as r,I as n}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse(`{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"Coalesce","text":"Accelerated Web App Development","tagline":"ASP.NET Core • EF Core • Vue.js • TypeScript","image":{"src":"/coalesce-icon-color.svg","alt":"Coalesce"},"actions":[{"theme":"brand","text":"Introduction","link":"/introduction"},{"theme":"alt","text":"Get Started","link":"/stacks/vue/getting-started"}]},"features":[{"title":"🖨 Code Generated","details":"Design your data model and build awesome pages. Coalesce generates the boring parts in the middle."},{"title":"🧩 Extensible","details":"All functionality in Coalesce is configurable or overridable. You'll never be boxed in or get stuck."},{"title":"🔒 Secure","details":"Customization of table-level, row-level, and property-level security are all built-in. Read More."}]},"headers":[],"relativePath":"index.md","filePath":"index.md"}`),i={name:"index.md"};function l(s,c,d,p,m,u){const e=o("SiteFooter");return a(),r("div",null,[n(e)])}const g=t(i,[["render",l]]);export{_ as __pageData,g as default}; diff --git a/assets/inter-italic-cyrillic-ext.OVycGSDq.woff2 b/assets/inter-italic-cyrillic-ext.OVycGSDq.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2a687296748f6b8bc8076cd11bde49cd27e4442b GIT binary patch literal 28332 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 HNCp4^t1hY; literal 0 HcmV?d00001 diff --git a/assets/inter-italic-cyrillic.-nLMcIwj.woff2 b/assets/inter-italic-cyrillic.-nLMcIwj.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..f64035158d7e4c01654e3f23dcd6e8299928a28c GIT binary patch literal 17824 zcmV(|K+(TGZYzoV0yihRy3;{L*Bm;yv3xXa1 z1Rw>42nQe=C3zL>7I?PUshK)MG!ta^L3b^d$O{#w!;WOi#caU z9epdi^~z}FXC1zMc;nw||B-~iJKZ7-`H(Kn3lKjZ5N5RqaHof~Lr#JsXW1jml58&> zQGi~|0cnL4zB|P8}0t5&U@c|JdMvNGlmzMw!d_sgsBSwh~ z<3uKERMdzOA;C{wc9u@*KWk~1ZrQFoOIvrA?XoRxnU;2`Lr2EaDbuDiOouv|*;F%- z#gKPJ>8fl=HUf*mFq&P*op#SRB5uMwrI5O=D|BV|t7*jYszMn6_y=vT5wbG)8zDBN(kmfH1b)al)=7_XtiK52#|ESncdAW0QK2HvRY`_;WiO>pz_-Xk zH!T)&YR`MAJa7J@pNA;Ve=XlUuY2Z4Y1bh`Bcza^p&9I}QLUiMj74g|4NyqjXq$hi zG1}j&v`P|}Bqr@5l@eV;qy>fq`v}SgHrfA#@*v!mV~$a+B6D&Jh-sRueXL+`%jr?vt30*Vq&Bs(79VL�!Z1%E;0yxE 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 literal 0 HcmV?d00001 diff --git a/assets/inter-italic-latin-ext.RnFly65-.woff2 b/assets/inter-italic-latin-ext.RnFly65-.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..9c1b9440ed419d4a71ba46b0db3951164f9e10df GIT binary patch literal 63552 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 literal 0 HcmV?d00001 diff --git a/assets/inter-italic-latin.27E69YJn.woff2 b/assets/inter-italic-latin.27E69YJn.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..01fcf20724f915f68a974ef2fb85f86f3039b1d8 GIT binary patch literal 46048 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 literal 0 HcmV?d00001 diff --git a/assets/inter-italic-vietnamese.xzQHe1q1.woff2 b/assets/inter-italic-vietnamese.xzQHe1q1.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e4f788ee02bb687bc1d5045483ff0d381e7654e0 GIT binary patch literal 8784 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 G0002QVTgqQ literal 0 HcmV?d00001 diff --git a/assets/inter-roman-cyrillic-ext.8T9wMG5w.woff2 b/assets/inter-roman-cyrillic-ext.8T9wMG5w.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..28593ccb8a4d849a746f2b970678fe426cb136e8 GIT binary patch literal 26600 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 literal 0 HcmV?d00001 diff --git a/assets/inter-roman-cyrillic.jIZ9REo5.woff2 b/assets/inter-roman-cyrillic.jIZ9REo5.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..a20adc161f433a7c4e3d92306301b9228bcf9fb4 GIT binary patch literal 16780 zcmV(?K-a%_Pew8T0RR9106~lZ5dZ)H0DY_g06`%D0|eaw00000000000000000000 z0000Qf;JnTejI@~KS)+VQiTZyU_Vn-K~#Yn0D>GZYzoFWxhOFCMgcYgBm;yv3xXa1 z1Rw>38V4X7&~ydchCS#Gz^U?2)GLZ$;~)fx9VPq!mcYpnq1!@L^>;{*F0jecR$FUZ z6iO)D+m7xk=GG~2u%_f@f_0A}M3f5QA!I8ybSlL6sOEWg@rZ{%r-XO@6}qY6-9rwk z7S=XGl3Aagf2VfteNWPp^bim65V8@r49{4$60G9{g+&}2us@F-2(Lo2<%nmyfcXPAsEmOx;aaT-TYLA-g zgvewpb}BVR-ML%T9e1_+XSTRIu8ysxt~wvE#THu(7?>6s%t#g^w%AI*C@r+u&NSFS z!?YO379&Qd#efkr8h}DK?^}Y96T(bDG`ag0NywoDEJ20KvU^4sLVzaVq0LwZTv|C` z14y1JvebUtkK*Sjex9GxKPxfB0|W^&XiG7#GZLgqMUf&w#9A^4 zW+iy=)FGciT7s+#QV7M`LjC>~tH06w!e2Ax?^P|^{|Jdg?L}zEBROmDjx(eq_hRc1 zV(lsh-~$j6AyVM#mLRo3yaZC>4oHYziCX!nWJ|kyDQ1We#|#&!v`Om_QWZjOQ|_<7 zqO^Ag z|LxCM>gQohx?AS&cd2u!HPjGABuD`9BFByk0|5i0D0M-?X4I1etS^BES`g^zkt~9N z6bNJ~lV}D>P(cz{NrEb0f~~eA*hz!DjYD*TB-l$5xJZI}3Z&b9(r1+npvb#yl83Nl z2RnI^NS>jR=NyU`K_+AH?@ubOxrE?5zK?(a5HSEGfL9XQB!kX)2>@jCl1u;sGFnn> zj2C28OX#Vbi5JWktIZxrhYIpYTimo&U!G5i)?Q&yyoK6r2DWv;-31u6PL4a9i$7$)2F)vjovc;xwPK zkBm6fN#La>Q69B!-2$zOXy>J5lik8e zjvr!tgI)_mcek^UQnXpzqIehz`x2z^vtH%)_`!k3m2{<9hw^N84vueaRwueKS$3HK zVb)5CK*`%upy*Rb{-fw<4R%DoIZ>ilo*ZRa)D#}Tx)+(E&Z1l5xQ48UMoE>6C}TlH zzi*VptQV1_u#IZ4I$*gm@*C|gTAPwGntNMse;D@@02GLZEstc;mQ#|djM-gg+ZvER zs*0x>FbwEhF5>l%PyLvBaHKdMIm=jls)CofEk*ltCopWRZAp=ynu*F6+rJToloGhD zK$~G>vL-8w2Vjjj4qt;E)-no*zrxzjk;7tzvejc<`$O~munh-(`?5rjPvLp#Gx!03 zQlpT<(il36?a(U*@j5wBtwtSH=jYIE2f)zvu-AX$UG+dD!}anu^fqT3G1QWWK`>AT z`){B4jRWge!1`BdxMP#wk)vBR%!sa>ViO7q%y$S%s0cN|5n7^%Fc8IrK$H+Btz2Cs zD>zAuy<{a9xk14Gs1mf>8v&(?o;nb& z1P*8i74t2qRtys`mCQ;6We6lqC^V_2!A8isXLc@nFa|`jzFM)INiB@Dl`AG{3NIK? z5rn0wAPyMV4Uk-g<)=CFtJ&wbEPa48K2Y@a&VyXcm6N)nbSph9zY&D=zMFA)~&X^R$umKua zfIz9Wq(Y-07+_SAB+mqiCZPc*V^Z4i=ulZ5&?r!ZK(A1$vKm2g^UV7Jb~Zlndl9dG z{TXm*1!agOP7zcBSrT2not*}`8#^CBU{HJtb~S_-uBKN0VP%Tl>aaPAF-fwx4W+PT zC!&!JAZLJ(R%+2eCCg{s%XQqbgms+dUoGJ$TrEICQxG zMBnMk)3xV|Uaz`Pf60B_b<26H@|OEn%^mDc^*!5C*|O`A`&r3~`<3%u;cC&U>yzu7 zC1=eQ=W4&z|FGp<#frrx+Y}HGPyj^Xzv5jWpdbL|PebG^h-Z;G5}2DHbrY03fV~fd z`+#@|=!YQm5HOE{{y0RR0P;y7o(Abz2)+RLOF+K_p_d@^Ap}1I`70oPGKd}80|O`k zvX0Z}<`X2jxsD7fgkn)4F zs=LU?d=aObus|! z(Eb_0&jmb3w+NBZq5y*2@ssYZDSt(HCWd%C$xKd(XvIX0k&ID1=KIDy>8TE-BIPD( z_CQR^vqsx!#*H42Bi?w@T4REL_hSh^%I6vYB$N)oSV4~h;-2P&&Snj{J3XGHHnY1P z!)kVwqyjWG_X)t8NfYy)mlj(EU`PsfjNF5CwC&d3@(kPZEmIF9=~9&f@-qO`9~7&h zQMhF|zt-e8ee+2;*$yz;WQV=H%U4l>Mv@64=$4c=l$dKUW^X7h*&vo{D4X^M)4bA9 zzE;w_!@QYhj3&#RnI#+m-XN4>8N!}CKkQ%50E+FvCVM*iB5M2a8J`c%Iqv`f2QAuN zaIacLFhI~|&!&sYT-PiGvNU|lqD=TKD`V1VRVh8pqGIBP1e|c*!9E|h-#N5hj8j-m z?qg-+(C#FT%T-<6Al|6c##QM->NUMQy0C|rAL4sQVgVht+3q_oo3xulo%1wZz9(V7 zhb*u&+&};bCAPD}_K=6e&Cg$;KP{BC?g{T3ufjydYcL~iWrL=ww z?w*g2hcr<_9i%De_J+;$Qfr3MF%jYK3z18KVF%zLBg{$d5E$Z@g)0sk?J;F*&2o0$ z$J_b3BhpFugAWQxEPI50kn-`|*{|(zO2er~RJh3zmng58d0L6Cm7%JHf+g*001fN{ z3-$~`D@8K>bN>eLXqS%<0OV#-dhVPU)emx68yL4kx- zrDm#&DkbKFp$aIx?5tL?<(;+&Vte$~g{7==_hVK-NZ(+WjvK}=P-xy(y-(e#DSesh zgvL@m zlnl*)X!#s|9R64angj7!89*OraXhRl@tMbTw`)|l5AjPbA>Tkwp2E_AW`QZLv`t~) zuPkx$rjB(DUr=D$l0PgY3+>Vh&A%OQ90)BfZ-UqktPE)smD6*5a;nZw)cM(I$LHp| zVzj($J_}Yu?nec(4ni3eY^UedZ_!n{wx)z7ciGFXU-h{P&eqqItJx#hINP}-8R3?( z$L|Ii9_T@*;=(=cf*L7i*nyWG^DW4rC?m5sr;v2sL|W-eb>(&!UxjUp%7!YS`4sH5 z9se-U;oLr6;DZ^4KbhWh@y*{K*{D(!+J|1I zVZ?|v?oP|B*)?bG_VCDG-NjaY{w~+hcCM@GNqSVNqW@|T}FJT~!ZaixUWxJb{(L1$d2CAM7~mhwnq6&ZE}VtvAALfp={(9fMa zqZTYtpJQtsAGUgUG~$n?OB@@E_FWQVxlcoDElE2?!Wq-~#Oq(GWIy5dmNr!y3$)#3 z$E=P-aih9_1uG^+j!2JD;p3}A6XN&n z*!vI?6y%6ZtyunF<8_!dOq{{g(z@s+p)gcWHkx`mT*0*`>-s>B^s4j?Vsm2ZM*|qg zcm$9T%sa~vazY$p`60k-&gn9J9xhHfinHVZ?0pCK_46HUVN*+_9 z77^9n7&GJ;GH5o`6D=dt6P-3>Fl5NFF(#@*EnK3_ss8plqI9u|P`CB*Ih_s)K|)hW z)uHO&tF>7~h|fVB0fv6wLz1LdCQV~>Ht0ow1kk{_5S!z_FkYdIFYCRD&!|~#-aLPf zS?3-QS#$9?FZ-Pvm8a~^mC)_SIQHW~W@fp9ExmeB90g<3d@jW-jDds?KoCXa4whXa zNSQYcn+SqrH;7^&$piti>x73GgaxcNv(2ZAzR-AV=9foI9sV@%|7X)xz5VXpt2?cE zU&JVRZ8F|#G&9p{WGdzcABYmp=7VzG){Vi!hv(xO0%P*?1&5EQ&O3UN#}DzZBtNLU z-X(ju8AHxx_x_Q+0n!%>YoB;zlrQ__UXKfj&mQoMr2f_Xg|4wR-!%yP%Eu>cOZgkP zMj%}`LjC}V1w<)t3qgYf22y8~I*g3z&hl=lsu_rg>t)npIAi5mnid8PMGf|vD-R3Z~aoef`hldw!teQ! zsW1CCF;dGwTq6RdafQcOuWVW!@s13 zE>ZtZfB%E7{$9LzUS6&Tz5M^m-Wm=%G!6#?5->5@KIKK}-R93(IWX}E6ZQ{_ZY13c z2pcl}Q4lA9VT{YzS05c-X@ADZW7|#9N|Ks?_W3kRmh*e#FMt4yKE!5l4fJ(!QCt)L zb^g7*~>CJvyR$T$T7t_I%{@fX7%D^MaG~ zC=CaT*GmdT<`-I~+XIM=jRDfroh_Zv>1YVV`i})XKL{jwZBwVtCyN(YJGyh^8sqEc zwV>^zLiSIAepedEX5i|~3nq=R%h?m0AQ^5)Aa0_*wsNeJu?fkVORt-`mSkw0Im5pOZ>Q6pLJ=UEkDn3pq%U!As8g*NqaW#8&wy7?Q8 z7D^*}FG*uJ_$0^2z)J{bQeA&{nc-KbKgyPtW^6mgvS($1$J#z-S?7Uh8FNZQOTw7A zK&7noKlqM){%r*$`>htva9Vx#q(CA7hV}<$Y-*PNVZ9?}Zu)YDeu@4?AYHz7{9S$# zo%a|V*jpe+UkjL?iWG~dJ`0##+gh!@=B-y{Nb`F$i0L3aI;0qGAnKQxEo3=k+Vikh ziJgMWumgw|D{%Uz0VNSgrq({*m^j@e$JQt}pp%ze$Teo0&tl~`*-!AH_}$3p*6I*jbGEg1k zS?%Gy`!n*x1FjaPHP#XEZhh0Ghi3sH%+B`z1nKKjLEinDnO;32Kd$kT)w*>{wZ`4M z)!MclV*WmQ7)4L2ULW=fb>1`9MV-n*y+;GFe}0i^9YPD4$2jStf{8~F>DS?Y<;mB= zxuX3WziMHk@QMdA1vuF`k*Q~!bsuN#%o^;LDn%I0Gjkl%6KIhZ>$iU| zTZV7NKdy*ZRD3`_R-t;r>p$gw^z5@Ae_0(k-u#u+4QP)kXM@KCAQ9lDRUiNk5wk8}7v5*?m9_0>GnZ)b zHXOqh`8Hd>M|pon*q9(r&|1CbLE1S)5x0aw+D<9C&<)B~1+x;X%-X7;FgA9K$?0gS z)7Th0O|}|R3;rgCc14zyPbK9ZJCaaz@Dgc}RF z06O}VB*4oJcKzYlSQI*e9ILZq1U<1LIG(s{E|6kNOGwf#G!}wgjvUDFSh3HaIa_i4 zj~Ir%e1=i~VAUN9~-?l;whI>`rG3>&8GQ|<0?=~< z)P+(_zl`h{OONG9#6FsNPQRAO$V%H#de}xcc;sdP^Eew!7wL880=n>M&sf*G`}sGY zvfiu7nZ0pkA1>yuPM*-_8Ig11Lmu}dsj~Ko(L%lVjMH?zkI}-Dn|OB39}OSQ2iCUu zT!cIrx)$(9wo{J3WUw=H6tyAPo9mCv&}3_k59d?)BdOO*65ws)#m(7qG3A#pi&2WR z9GyN>R-Qe*9(^qh2w3X-3x2MP`P?&T6MZ6F8e~s+`iL92g?aLqK(7$J#lzBEn5+wjyJQypMRb0K*!G|hlhJ58~eWEefdUYAV|5i z&W*AO=3_2VGhs7*ne1oDNektL<%tVVldrNDlM_Ni{K7(V)g;o{nT2VI)oF27VP+Cr zRLEt9hsEt_P=nwLjMS?605o`%T$GBm_>Rh<(02}SCmT@N5>Oc*mOT(t?8#K2xY`v`svc_epS#Kb$SpGKe{!-zdGxpV z1tZ?cn}~-08tT2%LG^F@MF@Ze0KGCOKmgF#I3N$GN0Y5pOIz)n1ew}WV|S5kioyYm zv$aEg{3r-<3&;(ckd7$@NJXN1!DImw@wU4|)a^@$+I%~$Jmp;6$~2iOLj~w+*%Ssy zK1I0=FanxB{6B-_FiB$zwMelwN1_lwU2*no095^IK&Ml;&9G7-5<@>{S{B8t-J{f`tf$OU{=+D*a!LiM{-7GH`nMQBP9|58e^c9|HmbI&uA+WS zJw?4z{f7FeMzBVy#vP3h6dB5X%4N!ernY90<{PauS`W2Vw5zl`wfnWFb#!%3>iFus z)%m@08`VJFNZmoTp&q0jr}|Q(sZ44)l|${K4pK$FB4Fad6L{Ew;5Go{U~Gmx0LzJ{ z*ti`X-EqJIv=O_~r1M)!-N+%?DoG~NY$2o_C{l!JL_*RaFu7wRc8Vx6$9a@xmV49y z9%>M%0mSBjHtabhGz__LKU_0z55yN{&qtKPf5r1N*C*qJ<)Wm`)Q(=>ZI)!x)wk!q z2||HF!oFQ5$te&wU>bf9l3c{M6qy5Jeq#ZMS_4gM7gdtDHm_Rpfbh>- zhUIy5w*$OAhDGUt7rk+VVU9bu$Pls=cd9-RSDO2J=w#EiJuVI)EB7Y~mR0aQgH8I5Y#>!_I6m92^m{UA4HhaxN<*adsD5tu6= z5=u?O%c5n0SxVT)CK;Ea_SjX@-0fCKq}cORM?lW4fVkaWIO*t3nv2$zahx^UH``Ib$S&$Z3QBb*!#Qk?S3>H7r4d&#wvI$9LffbgyDcr^O}xMX{AA zuq1N7>U*eS^=gYXscv@gvi@$UA5`OrUK>YsJZ}E+O5S<_ma}H0d-L!nEZ^x^r@KAj zvx_uoy6o8WPeobJH#y{?UXZp>PYOypU9FrV=S~NxmR&`SqsZs0arU1Epr5hWh`}`` zi?NIS@jQIG#>6R3@abMJoHei??zUh8fz^JDc;K?c(DzSg7$)^_hqEiH{Un`C0&Cyy z(w&Yw+6D^EcVBwb(H6pM`{6jPvP)M065F>OyPcvqf5^!uYidQUh$I3TKO_*Z|V({}+&T+f2B%VBtXNSeU4aHVhqD9uBLG`<;y)Mt? zYCOlDQ(tXyDDG{XGilOvO8=%@4Pe9qhp2J*nNJKZj$hnTa|i!to~3d8OnfQLGUH!M zvdkap+l+_!uT^dP9X@{KZbyZ7;R9U73}fioj5trHLJ%RqIxPPKK@FokLZSzT9{i=l zU-pMDSciBn)0~5D%}*Tc1K3}+j>mNAOytbOhW$i<03nzPZUYyNT!xD;9Uq(D(R{q4 z@6?{MG6xS{QACv7Q_|==eWHEHelzwoF7LI;1a=ki+|;XXOFlCRi!%2|5%b2jP2CTd+zBfzwT*FdkA>H$+BOs>6f}9Q_A$6 zrzw9g9|G9eIYgY?EmU?N&28_9vAw-@TGmV!-Sq72zI6WFLp|N?JCS)Es|;N?@p+hI zqOX+*9C8(Y51w(H&pqwI1e#{sFr+wR`DTO|W9A{4lF}VS5zQRMA08|(U0t1dNv(m^!IQ>qz9KDfw*W`G5 z>!IB6Q_qMi;dXn9jNE#GkG^_R97>-RUUxJ#v-A+Ibi<3Gaq{^P6Cqw_%-Wa^Z@!E^iX^E|d!P4XU1A0G{G&q;fK?8VVM zGUh~P6d!JK6+_J`17`DIqDT6{v&@e~?-u;x$OCYwt@I09O5A1Dco(e-H&)42R=a*6 zclTmOvcI1YBV2Asb9b`kex9VfaED6=b+L}r8eF63dJb7qf@}EXa3>toEs2E9WkwwD zCYqWEq0-mrh7_eqi{+`OfH&0cX(7AerMGfjF%md#rmXNL(!Q)6nB!3@dMVBAS3JMU z0?$bBglDYgqEi0iR@pBfW2|afvnxcXUqHUeyb?8zPB|Ych*JDgQ6i0>}dwr1$)9qd99{?{F1&_$fE1(sddbQ zVviv?l<*>ioj5n+ohC%>fmDlv4l8VJ~a zLV?h*^T2uK;VWP+C4`cQIm4?ft?xA`51;WXIFbx^52_1{2Db0({0XY+t?K&w;Rtwe ztiUTLXf#YtE@Z3PbsDM)3k+9q5A^68Ge(O94-1o-x)AZ;rjkz!+mqFwB@<`ecae-d z0NI{hdV#(59g6h)Ix6VlvDPnBTBgzZD=ysQN* zZw*l&%ecSKN^z5LmK=H7N8lCQCRb3|Ordy-0#>bJy$})c=N;T9EspnnnzQARX@^IC z#F2?ZcDkVphOBKa*Vq_N-FzhRy`+pxOkA35#it$=bw)|9TU;xCiz>lnOnf+Sl|KxL55S%rNs95NXZ zd`jZ%ipjXsEBUmvgqOo4j94t}H9T`qR;YE{m`Uyy<9e%&{rm;b^I1Jlh~Y!OLTHfa zN_CLMC=+nBf+n4dgLT$I7sPt(NJ`@S>RG>YqIT6n(UHCbLWLtF0qsxR$Oh6kJ(`!~Njb0wXS`osNWTedIiRb^CG z)ELmr2?w^odi&RS{((^Xy~je1VwRchGH?a1*tIX?i zW>^<#`ARaaJ(%JGET&YFxA4nCWZ3w^zZC1uW{Jca_uBxkXg;(|l-fdk6hk3!~c%j1V^U`VPZd83cN93

KZ;k8ywA} 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 O1{o}0SRMEP0000Yp_Hru literal 0 HcmV?d00001 diff --git a/assets/inter-roman-greek.Cb5wWeGA.woff2 b/assets/inter-roman-greek.Cb5wWeGA.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..f790e047daa346583880da2be470431e35aa6054 GIT binary patch literal 21776 zcmV)1K+V5*Pew8T0RR91096nG5dZ)H0F~eX092&_0|eaw00000000000000000000 z0000Qf-4)J030emNLE2ogenGLKT}jeRDl`*f>bYT3f2_GNicz%3;{L*Bm;zQ3xYZT z1Rw>38V4X7FN+22R&1!9099AL&6-~nb&9hR8HunLjFfWr|1mj{u|WmkXBLqqhgS6B z*89rbGY9tSl-Ogqov%UQJyVUEtk5;ACT%y9qe?^~nPX;~WyInH^VH$7f4KDd;6Hr? zD~+r>XrgNl{!tJRyZzD;8v7*4h39)-U3KpJUteFcWXZNH%b^)zj1jZ}4ebV#zrJK! zfB`oM;|2o;Oz3V8<6%w6`j(*+JLTG9dSYTirgoT+*%>x7A$zKr%yxNq-PwfJn}5vB z&*5>`VW}d!{`|A6Y)A-2bxqCbN|ypUE)cx-9>LRE ze)YP0W+(X!O78~cosg&Gyv+fz0u~NX9y=ll`jQTS;raLW$K1^|!^JR+7L(D`DrK=U zOf4-fRxK%u`IN;fNl3Fvl30;EEy*X6B(3sFFCnQsNvc(=B(zt1y*){*CRdoV{~lR3 z1`Hv&UzWXY-g%E?V;nZ`y#IN;^N?h5B+KqP8(!*SiE}*Go9FBr;($XO)&z$rtQ-sx zjyaU2T z{loUhQpb!Qe`(Y2@O(e^?cDdpcKd=?2!*KXZ!KXK3W-gGOo&XU1o3;Wrfv7~YBzBd z-yMT}VQBgat*E@E=2tk3Kze5GQomIRM>oN}L<3wPd)&_fTD~-WJ^jBW-=BzQ5I)Al ziF7Y%Ynz=DT2?gSnt&C1;MRZI?_2%%pEWzX9$#ArLi?G9H%cC6bvT$k26Jh@LZuO^ zSSN`kJdpymi#CgizgM+v|07R2@S?bp&e}pA>|L{=XdH577o|HF<>v<&0EiNZ4F*Z= znS@SzASHPrLfi!?@irvta&(9R1eP{PN;VaEiJNpeRt~Yuohy$kw;^?{J2$Oebnabr zg^Si!V_9^Mn&)e7&LfGkNC<1yEizZ82tO9y>fhnw5o>Kj8rxEF;E#QcX|WK#U)k$D z$yo?NL|74(Wd3>x2Gz>7zbcYgYam#WLcrZvL5P(^SS=RfppbB2gGSEs7S1*&%msyc z6y`gy&7zP7g(VJLYFStb3acrsbzrue>(?4W+A9tS5JY_!+S@MFd7H|N6-VFI&ky|LjemdK$s>duvoiP2Z%_h|TEz+W&ctAklq2kRv{9(Xk9Z&O&V{VAGWJsS&wVi z4jtMlv`drrtl*#y2^?N;5~QvjuiC5HgC?2;E}$pwa8PS!qJk;apRHE} zxvi}Yf-kc>C}o82wO+({X!a9NQH{~`LJ+ctW$;pLlvww~`0m@l=j?X-=o_-#T9Pue@ z*hw$BPZ$$1aV{EMcrgu5uIx{ff%q6uI|WQT}Z;!{ik4aQgX(0%MG4kW9yRzHk$5z>6@R^95()y3rjEJa0SJ(^!bl$jz1p z#-zZJnr+|z8D8WiUgnk2*;+U6^4?+L2Yh%#viDB*5|oLn2a1@{EQSdvYCiE16SH_~ zk$|d4lQ0m6A5{pTHjU7Z$@Z8sEC*v&($6X;tfoOm^;kp2!I5El*l42~MwYpiQn*v$ zAk~CE4KWPXImaOHTAzX9VJh%J%sjJVylmB;7Qz=29X02g2%rKsDy1kQ0tE|#n#txk z+7m?#3I=LbebcDWm>LFxsll$uYP+QvfB}U#{HQ9nwpK-;khu;F083^AfFELkCb>4ub0ac(t0g&={O1IvIcH~#=a;HMdAw?>O6|vk$Amu1QDEAY%a*P1K{~HF1 zf?{ebA-ykQJ(Q3>n6N$^a`!}{?+mW*4yJpPpm&GbbA#%+3F)(uq#PB%jMUS&U#iB| zmeAVL^6_qa71H`Ca_m@sKKg!+j9uF7N&)3=18(hs7S24m!NB-hua>oxmhuf_pTd_o3%a z&WnP>LwAR|UcD_W?EH9WH1}c7nE8X$k?^EX{x)N3p2>`!PY?)9I||wUMIwhm0Dr4h z0f3*&#P}U3b)5ojFg+)9<1p9EmaQANwvA$ACiwdUK47MZ21z~)ODtd^`Smit5io$- zrU05ioj7!OR4f5hPpx~ggdm>fbw1*AG5{b^gn(*g6}>B~g}SmvReP^|=RzgOk|c*Ei)G6%7-V@ex^ zEt9Y#S?{?6qZ5a&ACph;;Zu?5uCd9R>5Q2;`?QnNr=|gg%%8oKoxA<5v0-G7pNk%f z8S8Q$zwADU#cveuyz=U6!===DWY@sD~(pc$&Ap zObrn~ootNzBLCsDTNH!KOTKca#! zQq_f$djoOC9V4uS^ zq$ChF6jlmj47EnNtC^I6S&#||rIFb#C>ihyC@^D27BgUqBFGk&6YRuwx6Stfmy>`; znHiH>*rDq#33t%BsnQym3DmGtu$y*7P~aS`nD3@@HfgE}Ii!_oRMGdWc^sJly1Ap0 zr^^bydhsd<)lBjhzfFA+pn?cYK&cR2raY4XxPQ|nVu*A!`_lmvj{~CsT@>+KTfoGB z!F>X6Nno8btLg~Gg%0rI_8O|^b>$Hq`2sL~b4q9=IYt6p%Scg`D#K&m!sv2b8b|bK z>L{nvuBiTP;(;sThRHTjP9_vjaZTUNZ^}=)QW+Y>(d0Q}&ZINF$BUKp<_j_I&qmyM zh@!RlVhUU!DNnM>!uA4nWH+h-FOi*&jqVi%DuLpDb35jFi}2V3t%dJGch zOT@d$yq0>^E~|dy;&MDH9mSR3MW#O7n>H!Lg4l)niV=;Yqa#d$QstqS-;#FFsri<`-T`PHF|L~}o^tzGhwtiVwtmfmd9;COi+2b?+;S-C6xG8l+ zaBjpy15K9OOm>muP2-S2kuNf!5z!4URW4dmcEdO#Nls)xC6hH@m#t0Wcm##rG>-#%|I{7CHuYKm~&t#s<4;@6H@^H)G6|^TLh9; zoa{LHqTFB%vJKjz8nm{qx65;#IWur1ImyvOtYz#bQ856Q*)BkpoYwK6=# zyMzIr#5TN{@UXhY*LK&|Qrf=vOFVnka;!Y_rtZonD^@f!*kTSp;e-3iHs7!8yhM5D zx&I5yW1Nv8BQ`bFahTdg?zb(W0oZd3$eWhDIQ5EG6e8Oe`mX(QkFkailJjZe|HQJeYqCkP-URhOmL^couHQ1?!9<2M%1E2;0hd5vg_RLK^1d%Iu^Vz)V8 z*oozSMYF?fE=%0|E^LN_zO@+EihDR$vLuYCTq_bc^n6w9`^sf(7Pn7kQ5yNh@Q}Te zRo!&Sh)D9hc;+!2yt0YwA@%4KbHbDV1s@n?FQObR8DIul-gz>d%GnYcFsTfB{*HoE zu!M$OK5H8B2mrhQ|&4aX!RPUR5zB#;GKv;_npD?F;z}IXQt0%P=nwnx^uy%g0FU&N#^V0{t$(F+F^Owb+zgr8Nlmo!NHsOnCpP=YEAu!|wi6 zcCY{TFMcrmEQzoA&7l`vCq1(KH$fWxQ68c8@*`vjM#C3Y-r1D@PYA}_^3aY$Pt8c* zq0`$V>!Gl4`!D@LoJIDTer9>ywO+QLM?d*x{_;>95#sxIee~WmalUPr7w7!{Fu-+> zR3T#N|7Ns|VM65EDbg7g_mo(2$H(qqMi_evPHor|rVryUKem(rQfll!nTX`CoCnOH z^ppL~`8EI&{f7dzc6~ca3jRPsJ!RRN#~;vRrSJQluQ#lfjsNiD3;(>c4!^~B`!{Y* z55B9eIQq@Ic;fQ~?bkl}aN_ost{r)LRYI)XxZ|wo@U8Z~A&^!l(cA72ovTQ`k3q>4ICVj9w>_zP z+clHU+3nC1&zzWg6RS-XzL3?16W{H27@AZo6>3GBRy{wcpJ2&l_Z3ztm_^>aF)+p& z9pDxOga{)2Q;UoY35XQ@T0DHfYy^7H*Sm|Y(8pRE&>1H`oSEFXL2_#4wxpAr!kQ26 z<#x>wmdf<3#a)WU-9jo=);2SX0+!DJ%8Plm1ag#{Mgt~>rvQd2JV;zjp9J&nnac z4BEfomZiVRmPpju%hPdW@GomUx|PX}u`E~C)=*29pW1JC2_f*&! zr>4f8@8TCBU_b!sNiPk-fPCNonaz)=>9a$I5wM#~RtwY-VKR5|#5w>b*U4(t1U0)$ zqSmWHiVFanaOi{g_5|F|Xf*2pxU&(X&2Ry@v#e{FxWHKDAE0#MDP6>&CctJb`m)LG zgWrZ^jQXJDy+IH1|yBboKe1}R>7xt#ongtsvb&hB-w_MK$D0cB^DY*w z8!sS=qhDRB+H0TRR22RRc$-KA;x^HlkoBpnL{DVR4c87j%k;QUV=vcby$d?hN=UA{ ze`wjfLm7KU*wTQ3%c|hrB1+D(qLK%2(&fvT?Ug;&1$silt%Ij4Z|5cnYWLK)fzU>f z9(Wp?a=hy4h8GnHdr!u>-0;1y=x%MaAWtRUTAb-09Go+6PVyRoQ>I%N{hfvK;Nlm; zd&j#XcYLm4sab$%S17aq!{{`Aaq8=$X2n0*IiMR%R0}XDzxk+6+_EkH76^0j)C1}j zfS~%FKIzoe(J`y*>`A{#`k7vtWZ=XK)-$qVvpe)0FK|+hsP}?EN3xZ?R$2 zy}8J|OuCo<+?&I|xtT6{+>^Jy%FaD6D*6t|Ay)yo*GD~GC>I^hyc`&HAoA#F+2iio zzNd_|%P)=>)j#vx*PVFR88KeiSKAHDCMPRzJ-h6A+U=fqT~0Cx{@lr=6RVhK_eBZk z?I17T%-lm*ZMl@`>G6HZAAvwv57fJvKz$2f(1L-U%N3>gGiI~QZPltcJNB{bSKfmA zD}k>URm~$bw3Ppwc3!=~=MHh;RFHjJhhivG{$cUy(_L(j3!6{*#w^?~R6q}(Ybw$ndV2`Js5=GUYUJWf|vE!q?u?6iNuutV~S+-`>#yUs?<{`*6?bPC?r`Xp`6e~+{P zp3nljHh>$f6&ksXWm+_Po|Gvx@A}&NUhejOqd0qM@=i|TjGpe>C~3kaT}o_U)m+fxvgSWfHQBWxQ(4 zK3MtK^NAPAJkKn}#F+CzYhg77KLuycH_EjLTx>K)xh`Lsf)uA5_6=CW4+~qz4+vPt zOQtov1s(oD>oyf9ZwkU*1w`;%Xq24O)pD9Ku-tOBNF88&`%i&R4d%agFk&A&c<5_z z5+}G1Bcz)~MVShdgc;T+TJ7J|L^!>8_66=I*J3xVc^c4ZDC^l5b4SF$%HhzpB>^=s zO9Y2vl5FGm>`%A0x?=yL#u(tE<0QM&IqvU2vyZ65x>k>cq|4KF^E!PPV7l{L-7T+D zzlK+<;cthGkDNC-X%ZYHPPr2-71v)4mWm=C1$GL+I)bQKg5rje{zW*Qc(sgv%>FG3+KY9`N>^&*E=tye~e#<^8Ne|A@!Q)C;Zwd$-1T zGu9{UPP>>R()cX79ufEJK+@~@mMx+ic?F?^wQ2eJO=;nS1rOQ=0nW&66So29dI0QI z2o1|SwJL&YDGD{@ss*`i>OM8#RIdOO+_9r8^~WU1Dc0x^6&z_njybnHErWR_)!-k< z+g4p|*SZI?`30IQz20I5NKODO@O;j5Wia2K95eT`wObAt$N$9Tu6Z&62;~LpL^aX4 z%Aq@Jm`i}DOJrICgf}>J3x+}YoIC&;uR)09QT5tQp6(=Lg;rwKW$7datG{#cgTZ(J z4qHHdfeFC1mj(_Sy_N${I%zdiF0Jf0)377nu`xF_drvpMNJ z^~V*oAY%rxBvv1o^NarMiQnxQg}*Yln8!Oev-U$ja<4pJkok=qHP&<>{BZ`cGW-) zejPp@dgAu1h)!Jg;+XhgsW|CQaFE#1*yrVc5a7?@M!&ozfDyoFeU6RDnf@vz=7r|URgk9jS@#uvRnW|^u+?7k7b1|*t*$kx#du*t!}mH zY+jkq-h`l2vG?byftu|NLx$0N^svRZgW8jYtec%7d|hBk?whQNnN)SE#{Uk0p|8t) z?qyw-g&fKP7!a`Vix6{59{wxD3p1B-ya?aj?su%W00(bFCTk+I%R*yL)GI=C*_?c~ zzOnK|b$zc{C1i5p1lpwJ#vAv66OjX)p0aSa{->95$Ig5J8`lRO;A{C^L!%x8#T{w- z!12gu(<7~z6Qu_9K?p8x=_;$ZP#LVxr{@RigS{fRvmTZ~=%ck-Y?0wl4ILOEInEO{ zcU^~DKBWt_XlzNP&JR3R`eE<%UU9-c1RSh@#Xs!P{{WUeIrQScMIO;d=f)y-8W6l* za`E6=YFBstJ0N^21Ovz?>OXuptMK^becd)>&ff6bA(!X2@7oj5x;LYGdF<>c3sur~ zv9(B+d@MDvJ@(atvreu>F0H>>AP@fJz9zq1C|z`1J*fs8$7>ZAi-Je8>DeJ8 zPoZK9wWFBTL*Er;Q5mf@+qcQA0(ui&a?4>)4CqMZ0jL+itMHDTx^Wo#fWh0{B-gQ* zPj0p7>N1w9wXc%>J~!UJ_VzE2NfKe`zE{BMN7M0|J2g%k=X*8D<1=f!^7dTt^-ss= zq#vdYP#MuKmWxaa#~gsNfDu5o0s&x={L>XfyBQ`KYzgyb3DlJ3WxiS;7JfEk$d;`T z{V4!`n{PM)$m$Wcu+MdXGl(K7RwO?K2gsNMio8s&Sb%Cl8+b4m^RYmSz!p2xqwV4$ zMbKWJwa(NvS{gdRkRpE>od{W$!UsjEc-4fgg0S)!n!K(aFnAlq>B0en7S1?dBq>P( zpv@vl%jo7~CfIBt9zm+QlrPY01fvBBrImFnKRW>Cw_z|zHc2U6l#lW z9b!5RFcfZ%qW972A(+r~zLe*nky@Zjx0nYBzNR#|Gd|?UblpPUfB~C-7St zIQ(uQ>3lK(2q=JT`e_!}^8Y&UR+htI4;TFt+th9J3XKyOlr@Ts3Pi0zMWe*1yTsqb zJL0oxq49F#WaA9utHyVXADJeyXG2to4pl(sp=;<_=p^($bP4(_`ic3i1&hhVwjR3y`@S^L>|yC^8wa-)myG*1 z%4>em!o?!U;`f-KWi4WdL?g+_gw-OeV5^x@azoAAGFakfsj zb8TySs+S+y+V5&S-`5ED`_*1|u*4yrpoCvpBfkIMj|+A}WK`@Mn@? z3UE3fXH2_d3EA;7xw3ixy-)36@Me^4V7`lO7>qTE-tnlT|9SaRtC`3Nn)zU+3D;_^{W(O+{8v~)QV>vpLkTJ@ zq;vKL1NmgT0waaXOnPLa@Y*cDMX`;I@mXUd-wF7 zIa;HImSjzQrI2z@&YwPW@Wz~_R#LwU0-DB8!cYEMg`O4rh&BiPdBOTS9hW|>TmPLm&!KuY z^z&C~a+5ko1XmV6@pRd`bzMV~fwdh(F=G%>!&g~U(}Gz$T~*H9 zk)>KsAVRc;Z-)BJoWUH0n2E{1i0qlGOGps%xJFs${7bGxD1733hN=JR)$+4vD7(DWueBR1h`)=%j^0aWj z2Wu|8*kl_EDKKO68VD?4&z{VCJb%!I%B@R0PuJ3JWpG^Eox?lT8ODRv<+_3KSMqe1 zl1#}F>VCRTMY;Q-2Frxtbns_2U91kaor}*`93cm4NAq0J6(1qzr$I-ugi4&|$p5c< zy@wq=f<3EH$v={A(6&j3yK05-5Z7-1;zvJC(qG!gtp0~TbqfGNk|h`&PEus?Sa*OA zH|k)(I|?CMMb3)>g(wI-fE&y&vhw+f3FfEc1ALlp+#4$`(8*D$MOzj*5#7v1g-x~2 zu0!o4W=P<2_r=#MnHeK|8Am>TsW8%`RMA(HUb;hod9b9I{2~o&RQd;pSUXojf?QZt z>h-#i4oniJpHz^e;61ur1bi6;VXO+$C{oC8%3$cH-5`4~GNll{?iULY7-BYoCP*}} z+`MNWc_j4pe$;^@BIXBd5mD;?j-zv6YOcri-bKK2c{rG3PDWXn<7zpicsKlVNjaxy z48BhN&ryZQXaaXcIV48@%9Tb86FTKRw(VA8EqCGaSSq72;HHA9Tm;{)Og^Q%l9c>1 z%pFvd=rVli#Q!-=t@G;vbrU+@4H1Q>QXW?)4-rT>nfSG!kjkxY@j6vfLhxUXZEn=I z9rR8|bUJ)F_mF-7GZ|zS7$dPEzP-+w-&djXscxyD?VJV1pR&h9o=i_X-bGqEX_v2( zB&|G>W)53*yhC!!!8w>iUyK>3f;5oVoDSTzh`uaw=GP9T#AI1b^N!aXaLMXwh8@c| z=APeepB-Zs*Xafmz~+o=-=&?PXpp}1VCO4BLnzVQk4ZzJaOqOTKSwOBU*7OlDlb(! z8OrX*y!zQ|{JhS;{Bcq3crxQl4(=uBO2NZB+}7y-h&m9l>5Hz#!0HHB1ZMy2VqlGh zk6Q&xURc-(WFevAfRvAbAH;?7pKQ6(O-ck&$W+3-9< z=i^|FY&Q5z4^=EJm$58;zT7!7r#7IjkQmYMN59CxUYFJB>-QwiI=Pho$AZ2?j#H%1 z2MsQPPvfg(`%>f{p7Qs~UA2*@HJhFaAK?i{|9|gSH=+#qPUSLY@Vp`wz=+(HLx}Hy zI(tzk58Tll8%Q`>tB$}^+d-|nhTuX8?xgDxl2efgaLw$4$dd%r#j zj)D^bLV~0Sk^>q(SN->3tpK=r@wq`*J zxFseHsaDz=w~Wq2-wS+zV!euTjw1wh*yXTegr@K-w4*uB1rCaM(Nfb!$`Dvl%vgTZgk(@Ot5$bpa{hs+o@oT1Q;N0~VkZBH9dWaK~ck%aa{zrD+5L&95l zuhGH6dSe2BhC5lr7SM>sn%D(DEf~uzJOxMR{ChclUQaIFG!1&PWoA+H`+XAmGixML z0RlC$-6*(8`km1gEpr9be-LY)K}z{nl%u!w=eC@4mk#y7mbRe{HYixy$f2q@h>IG_ z8-dJ_qobNW%VfsinqgQz(?#QWw0hYe57Hm|A$P$&t5*h;MoT^V?os;*a88D9Azkkr zH%d3qUBH&K0fM7YK>W~ zAWp25tA&^I0TQI~;vR)O}e5J$f|7 zwzixi-HtR3uX~IlLOdm+l0#%OrqzYIY~4Q1>ZnVZdR(lS!l1_!_icQH92RQ(Yi5dG z1YJLoV0SH=a(Qoq-TJmR?Th-cn8Ac*g1T=ci(e<=pkr#I%&~e8+=>!5b9?>-=ym7g zIX$tFFr@?CfB(6h4}=HPet&wGm7`p!-|MHNwx9^B#TvwT#*hYa;sVx$)ncYo&N9x- z8y;!bJtSF;;chI71LfgrNORx|p%#s+B+TbD)|C?;rCWRD%$vUX)JlfGt6l@8>E786 z+SvVqx^)REqn#fu>~{N=63j}44z;SvN0_6YhHCvuVLP>u@dJ#B@f~jiU?QixgbUib zMaaac6i$3-(J?GK6!DcL1QQxClq7+iqVKw?cnTEa5a zj8An8`bZ^vO=m+O99%q|=SZ%R_=#KZilk)fhu=xJ{^9!i#>Ut`2~8_RDS!m0a;G?Z zu9WV|6VN%}3Om||Qht?=<_MffArzdX&*M@Ibhk3kRcmalUSkoiCmHu z*A6asIS@T6aeV917rK*Mk_5Pl&Mj0?H|4wRwYG%A)#m)6-l^{9=N6%sQK?gxVV&5Y zw4naxbwp*mAmFtGML=x5pnM*DEF(PBnL_WLGb2d_P-+#!Rzd1HtW`X>TpiEIajY*` z>jhr$AnqE`szsMAqx3@X;xX!3LddrI8dLsAjqucItWXaJ0A`lmYAu=>nEK!OYwdy!pKZ6 zM`@v`92)>rHc3n7bbe4`e6Vd1=Q=Oof<%-zB8Bc9&Q9Tj1y?BOeY&1^H7S%naV-gr zNT49RFZ%Y<8NwFNeZ|PRz(^CO(TIU9I%GmYN>Zs1Up^i= z|7Gj=JCo=M_~lCrA|U3>{U*-m$*DyB?~bCk{y;j@cR?J&9C7|&}s{LgBX~hV{5FJFOVYBL^rb5 zR~E0qm)PUsAt|>sCr|c7g{$|pn#CNY6_M?{kp_m|u#^zmjPNg;DTW?t)S`otOpS*z zEW%{1M`;PY#;JSEFcwGW8cjltn&<%+>boqjR|@Xe(ubGQjee>$bWVb)h)n_&c~q|6 zx=92+wNmmL>~6XUz@HxH{XGAn9w>d2-+Lz-I>u|v(O_h(r~aqKhdoZg=3gszwh{9< zBg*+50;o=wc8D%09t}%hx)B8FwI#Q#Ak> zJ$I3K#G%0UQJa*4w5m;2zm4s++ymy0Zt~J$V<}Mx|Z)HCZ&x)Iz3rTzoV*eC?(n zEmaMi@8q~N-g9w*Q-X#6O9MjqKknHb!}nI;V;*CkdGip=<`@w)G*lAwPaHZG?H1NU zTS!O!kA#WUJ{j2&vF*$6MtLOlWYNfq9DE?}_+11LyrbN1xTd_?-rHNkoY}QlBFR5o z{dgQ^ZWBZ=$7(Zrb$e@zTpRCdH5!8zV-tU)*W|-+VX&aneaR+3>sgKlBgO#&KWFqm z7H*6*l?KmviLB9oXb9_-(wvqE3=IvvhiM7pN7G9BA}I<%Omb0t&bl=~ zy7_Bxo*J6?6BR?K;(TAC4DVr^-#RWoUU65;r+bF308(1s$CtHL^@DuAkHft~xre6d z(ZS#XFF)dLDV5hoW`SN-igh3wCa3w&WmEDmEnc5H%rV$l5URos*Pdv^I4d0&I_ac# zuOVB(bVt!?vSa;b&Dg+Hl5QoJf*?{|NIROGzx>D4`;I$T(BoM$M<-19Q?~@dE`|kK z6gUoNrrhH#3W2l8l0s-fH)|muYWDNgh}-;=eAo`{q8eaPU_kOba9- ze&%*zN|=b9wuu|+aFIQd%BqEntKj%f6vM_=Dn%mZR5S`9v0sAQqt8^spea^g;G|PB zq>#ad;92`;b>$aXTxy`LK>OF~NE}>+b;~FeM24nRHt_FQBAHyh=i0P4Mc} z^KYHF{N=#(i*^C|=#NSGf}d*Z553~LTL~c8=rT6^*2y1Ce>mg==ox&~2D&zK@5u{$ z+*)@Z!e5xW+e7f_%R_7SC_)Xz(V|?I#xqf=5j^XTi7y8E5uVs)M!uwSdUirpG#b zf%6-ABspG2D}+?~eIFM4g?Pob0RX|11$rqR7sL|b@f;Nh5<$Vas7Elms#a^n0c*%9 zSDrgLr-xdKK;poqSq_HFFZ3<|sGz$d5&M4y=Dh|d`^2bdjmcENk% z9|Z>~h&Vj6D-5yJSYc3}X>?7M3k0mN&^plkCiQBNb7%%f>Uo6Gi$r%*&3a(lQt`GL z5&eC*O8}T5uc*PX?CMwdIwR7Zh_hpBd;bLK{q4@lKiDsA4dgjLdwV)`U+Gnc4QicZt{1h$T{cLjiW=CtffBt#mMxD^AMt#fLj>WqqSy>9B(Esz z7t$ZkUH{oKS1~ah`kzg!89K!B@T(43g#OUcHpOF7HqcNWv;_=A@}$uM9W6^^I9D{b zT1QQPp0CZ>3(iMY@gksPdQOQazKZ54T544et2*Z=_J$w`BG$ZfOHBDHzzFdBAlg(} zFDrlp+BpR0n48hY@gpGs=@Lr|aX&I0q!`5YC>I&z^N}!#D}!|yC-EaW;I5w;h>$$; zS#Kc=FBF`ZAb?$WQRMpglLvl(;!l^RCnv!1#>6|cri{wHH0bxA>o|HQSox)`CsdT< zX_omVc;ZtIC_$elEHYs$gz0n-_>LACC-)r!`7D$xF1d8O$*1zQguym6x%Kz0ZDGJLwKZMS@(-H9RM5-s0f~F{J0MV=f8@|dAlYN87Z|oCvPQZKwKr; z(PlF?*t`dsRXQT;W8pBIw=+Kxz8g4XS9cWLKPr*q@$~%a{la?oW~c8BDZA~`->X%{ z#b}=S~&E?d^bm zR6xa3*}Z!Y`riBoifO#L5E`f1S-Laors!3}KgYRdt}3k8*jGc$!09L?cNDmW_c#@W z7m0pT<KtduM;##!& z%!mKxhAy3}*?U`(uEh)7MCa{H$`-sksGYSF!>t$VeoJ`hN@m;Wg&c`B!VUANMBHz! z!UG$rO9v9pJC8AM98Og7F%Dl~KwA;4+q}6~RYpqLkCCrQ)LGXKVjLnUvX>f)TdX!I zTBFmZSM7F(uAMI;pA%WmxJ?0NEVWI75}M|$$>oYw3>UN%o?p!QAS>SX3ptt?T$XCu z2i?d93y#8v6z4;k&jxdpnSL}18KFUG0|q-FtzqEirN6bb{KLV>;9E`?0I*1lz?U}P zI}zo~j%VNQD%&XU82i=fc?3auV*;v@-xFdhqXtH@)Rg=#D!d+`f9&^7XJ0?sdbFwA z0$pppkxcJ6O|v&8&6Jp%CBz+d2~pNV^$JgcE(%#eKm!k^4N1QZgVH9{8$_Z`_?T<1 zDBLM9Sv(*AWy6yHiGe^Pt;~NrHp#G3Q6HBjP9`WjyL)Jb3t&X&P_Pam#~K)!CQQa7 zH8>*LuUeh_STuCnO^F59A`hEzCNj06+U5Azs9Qs1{COM7lFx}ZBgWgrZHks?6iRc? z@06mGkV@@%a`cJyOF0ccHRRUUQ+kvRGlebOB%*ENgJ*-H;j(`&6nHbc>{1CnW=mds z^1#OJqkFpWJ3@?=kBBO*>5sMAvIGuo26*q_MtXL-HxlVp?6-2%Nrov6xRQP{G2A|j zP~sttkFtkVX>fu?qjMbkt+TAkQ9uEllvN&_wliMlm`e7;+Mgk102jD?W^hVhy@I^o z3s~71P86D;kpu93Kx*^)D$ZFDls@J+1t%mgQ%Sw#(WtP#N- z3Xz4vlyC_azWB&8VF{J4Lhk^~sA{~`6v|6Cw3CdVRasPY=Q((D5x9gEz-_)_#pPFD zJDspDI&kLnSr@-T=*dAz8rD5LFfdIzETaRg#}Yc}ue4=YY0DqjK)4FeVpzy5&)!?% z#B$n=xE<8xLS+g4CeMk{a*35JwKq|_cMaHlwmM1p)rBB9JQk^{6i6zxP*=kk9V(fT z82J3h5-0yAiw7oPk}H-aU^5iBj(ufy&wEY1=RYkBHG{)fugc&z{H2siI3S-YCK~@N z2X3FbBWpuf|h@+_(kRu2pEK24?a@yfpVKpu!<+BwcDy z7wTo)-NhnaXpAWwH|AvOU4ujjBuh6qkrfAsi_pZHr)F%6cO^DSBRYrVaJMkH;5-Hk zqhf7FY9UCeu`;v9IV9>_Jm~$wnDc$MYZxUpsS=v3mL*jOtXA61!2z^)PT03*H%*Xn zZzk?i-~_!i;b2&1@yX{2d%{=4LdQ9uiceCylx%;QvIk3= zl+vW=@x_ub-#iPG`?6qgECSXxCcwwnDJk-_c?;ob?LqPULSCXoiW<;0r1V51lI1CG z?-pw?v&4))?T1rt2{TyWCF+j23mG`!<(>^OFj`o~VyrZ&-%M-TkeJEr@vWkRD{DIy8f~eS(#)2n98qz0W3Cozem@$P)o8?n6(@qs5{SF#qK!rc zuqi#?LiX@pyqvXOaFP%&St1NpbDo&e4R5@cg4Z%sfFRr%fQ_>0><>jDHm%E;zKWY^ zbHe5nVd@sL9^+dqq{LKSNo#`wt~AMe_H$TY)HQuoSM?QRrT8w6C|3Q=jk+?n7oYKC z;+e6pyNf9#PUB~x31)QN(#r9=#tbW1d-lZlw&D(IK`wh9N=|dzl zDQObGswRiDsRE|{F^gF;UDzady<Ak(DLs!UtPHPmQi|8_{f>DlG@VwawLTJnv zZwyR}DMP9hd-vE=s1+&Ejl{*>azu2b#mUjK{x0zognrz#RU$JkY|kSJ;h&a7*C{EH z-4Q6}U9yMMQu1RWHD}2oG5?cF+~Ww^@cGQ^e){<=5npn3TWS3)Bb1_r=MOB?|Kh?T z#RevHLEaRS1_P_LPioG5ilZ1zO-t1qUmLC zDDy1Sw~N1;0(dB5^~U?YUjLYh^Gj(8Tp z2=H5M+toAS9h+eN9-vw1$3bgIKZ}VcMs{v3JgCfvv{+*fdR|jt0u~h#tSfMQPEo+Z zkQEbI7-MN`@a0?dE~yoq1(z()nBo$Xy)RSwSHIaMo_jY=&VR6X!`B2MDBO16#jIP9*UFUGF-s5+v&BNBfwens7fTKGWRR2Uu*fh0|PZ;TSL@lttA+f zMDEQ);(NVec;?5Qme*B%Q`ZbxOL|tH)yMS-J*}%o#euVrrBF4OverQDRSDT)^%^3L zd%Z?&V2m;Nd=>UC!#?wxaKcnN0eL}CCJr#b|WmN-ElU!yl7m99Nouia3g? zSP~a6aTJ1(FzGF{%Pi_4I;uu1bNn1QnJ=l*i|{F@=+?o>V4!DQm|n)f)Y~6IR%eTq z&A>3NvTgt)z}G6{0gUb1nUHRUVntSYsLbgFq)Sr*s?EV(D6U+yZ{NTZhYwZNiYuis zTX=0e9y*NeZ;~JH4)z~#cv4!d&tfl@o{E}q_~A;BmoPM=5qv*=`pN_ey_uS}3}iMr z6WiEA-ZYk**H{tB=0$vp*s-8m~A38eTqe+@h~qy z#e9rc3nE5L3!JJF#fwR9+7}Mf%7CkIXCmXfdJv5tpKRy8J-WgBzt7G_PkUSW8atl3 z6VxD}*n4i(Ql)Q(hua)7;#ZnfO(pLz5(R2EoKFYdRY`Ok0MN-9oK(|OS#?tj%}mjH z_F!gCg4;S>Mu|9egl_)8zje1L!yj?o5?QJq_AVZvqACRu*KIqT6;sWwXvUWGWS^HI z4qWgwr(&Kt9u|THzexsfZbGI6ENP*@Z23+HQkX<%pcEt8#vxVehEyY_nS|0)+Jg6F zCyjlZJY8fVY;%3Lu^2E>)1pfD^+;3wP*zI(J zH_pyIw@0-uJ-lsYS4!K$@51crAq2Zi^#CJtWIrT1@!L_&<6xSF|g~*ocTdwIGp%AG+v~&|H^2t8iHf;8 zzGP#p<#OR1P!cHPFXq4!Q&={cyt*B<63ZhA#@HM1K*aro9 zNEN}ML9ddG?DWl_1S@B(OHG?26UGGn-M2cem>Q6gT1ZW<3)nNutOoNLJBf-ZyG$7lg0{K?Ay$CX@bn9$s|gSB({~L=I=Y zXJ2*fi8H!y;3QftQ-v9?Q(!yRd39Ewc))x0 z)h+J`eQ*&KHgWOJw(&qmO8`)j*90uMcKM$=5a+;o)K-Uz%T?6Cc3>Py#Ic||JhICB zM_NvGCKWl@)Oa{B5FcGJOUbFx@JXgxf~FSl+Md{>DqH_TK3)Brn7|(ovk| zip441B;z)OYo;Z|&FuPU0Zp1K?j$42Ws&=+`4eB@yQ^M>kBAEu0uDf?YMJJ|yw<*t zwLjd-#rEy2smS>w^^9ZPgZ@yp&5c>QIP(guTsAf&=0&O3bkN#lWQ+HJf2`=4sgNL- zwG7hFz4a)Rs#`>(?aES%aZqpwzxUYdp^MncsjbZyx{=Xsvh5AN3tgeERhTi?M6cFL zJXft5%}@euwSI#34?T2A$>JMgB~+?ZdKv6TUlXjW0O4}W&@Zp;D$IkRM4KNopYpPk zlIXt$0`38EnxevoH+Vwz^TAr-us8o8dNrW{D1gQak!Sc5Q8}GoR=Hj^!F{IHbnv*=S($jrNX2D;z*ufKZU+jj;NTUBUzTAhgp1eSj|M7qVz#{j#9n++^M^Biq1lPnRDO+IzNZ z$No|@|7s0_MZaZ6aAg!Lhajn~M_a!+&^csKAP+?0Hy5y!Up2*cZ4^p=VBH15U*v3d zF#>}7C)wq+?lb^!B1!%MK>#X~1*3&~aeru2hDvJL@J)adj1ZgkTg?gbJ)Jf&M)MR& zgvG!dqf%ReFk_fxQDJ=(=rgct8jP`y!>kefo3F1-K7;jCdm?w=7$0#wPBV4TOFr_U zfqV>|PNaSr*;Pj8kfQsm6A?2wPbGcUnAhuk|0u{5&*&U1H5Fqq^h~6ZTk|1@uM0)1 zkP#s?QXpp;1oARxmj_(Lw#RLt^wj^2N}wo>~rxV}xoaZ-k-AcsQ@4#2C6 zz_J=l9LI;)7`;zTU>hV!%nZYt*!M%%CbAzT(k}gUww*G&QT(BPYu&fuz{#^PQ}>uL zJ*D=-cPH+ud%}zO;-=U!gfpOD~-b@wPO?zYLvN;$-}$8%DaM3^=m6uQ2y% zjW^6Q`N6CUsZVOa8BMuOL!IQKWNTH52BF(-V+@$EN$?7XbA&t2Qmissblq92E&R-R zfxxU;YBsZ`pHfqtOg~~3kO4;v%tT@`+B|c^{Ap=a-E-rbaZ59!3Gw{FmrwuGvist@ z)z%%AO(a~E(FgmEed3)*Iw6wVai7Bo%-g&N=CjbCttbVXbFGwgT*1nn%0fdaS@|LT zm8{-~+hQo<@mN}Oi(qe_09xE{OuuVF3ml6sbk4zXfwMEc`&~-ZpjzA2J<6$0P6glw zaH}22H+M=cpi*S)s)cmvq>W?&+~gghG{u@H3l-BAGgo_povz;P3gY%w)Rn&NE)<#! z{6E+avRd48y&{ZvyAq%LAhr6BYRr?A)5LG}<(R)E+gtT)JaI0*xNW;?C=CHv<>PtZ zM(^qqPsW@mVh7x{3KX!Tr(276oaK(vU8Xp5`c6i(HPYfYx6Oq<1c#6uP**M^ZHu$g8NV zsIF&69bN*>DDwN$q}9RM(-rqtx$v*O*F$1DZ{Z!M(qf1LfG?kERhv$oQsdz@Yq;I< zlbEN04cQE97rT{bu*>G{=v8g^YNJkb-dBm`?{t4<;-8fEo{e5KiiVLArQ?r$B*OA$ z(3DRR7mCLda?d|3f?i{Yg1f35z)dKfkCS=S-S*9l8%!GX8GYOsHk2`Za4qY@n3pj| zYPZLo+}4BUE@A4=o|aXdfNwM&z$x0Qf_uJVy@_ewlf>h8C)S!S;_hQ`CRjJ-Kf!;| z>CW|=tSEo6e4zZ?SkuzhSe`@Q)t7{_Z7IcT(alTM-zZ1rBx?NCDs9IS!SUKr@ITI9 zfbbEwoygE1q!Pi5rewwFCy2BBC0?U(#a{{}1g93)XNixoAK-9DZmF4l=#sk#`Tb9wCMWB{Yq*Vo1 z+hO3AS0W7AMUMPGrlOfF$Fr_Z`Ar!ls&M0176eykr?}$HR`$D?I9XnHi5+yz@(D z)kf`1dL~y&-F)QO=&>W4+dkv8v!K~7oUdx;I2}d;52wGRz-LLXrU@ie84Cf!n9l&V zSS94{Q!%m7LK=qw9)}OqxQ!NVvK||@qKzr|OVE?OjOOzh;hDX!$>s#|`sF~COe?@Kb7v_$N$);o z@v(x%l$y*wp;YVu6E#V&G@@Al1&8Crv_I!5+GgT)wG^zj)T{~?mVKZwfOVSddfrWwkd5f z)_JVjo|@GA+quXs)kvW0l`47GE`(Cn_eroc3)*lOgWW=QfTd>G2KCw+CtvTnoiI_3 z_6t*}V#8lGOSA8zCCnu)>m*gmfkhJB6pF0QW4WSX|Sn8+!D(~)I;no6Y{xvOpYScuEW@k&;PXY5)#6NZFm%BgjZ9cIQ=Abz$RsV7K zLqb)vJMBHu{QB9qE&!h7%2ggFUl?i)=>b zR1Rjf!F57L<0jzc5k4o#0AQGZ_~D1H{d*#e*Q`zz0188s|6Q(?=Dj~Y(5kv*msX1^x4E0Iu@yk;+&Kw?N}BhYz}B5LhFS zg;q3hOEN@I3_gxZY3bpY;`tpa`jvrRctbXOsQ@lnZrGrV1}MOb{0RA8&I&GDHg*j# z*d7KBmXMJ@zbK3zag-wh9b`rxO=3bFbnw%TQJPg4nwSui9EV+^{(iv* znOOP_CH}77_ep2DMNVU755%f@7%ywZaTkJna&}YZe{IqXbFJqBsH+s;h$ z%Z}jjR}d&Ujej*G95Z~ebo;#V6D{QQ_`^%!1ImqL#2<-*Z_!-kXN{Q7vb;c#5Twwf zsAPAK!6L)uW^<2^rQTO&)fE^Kg}5bpm~19{G{ya;M<~cWJ~k7Ao`gbzo|LMCdor?R z>d7e*dY1cx%l8IZ$0C)DaN-!^Gg-Zo;R#PZwN|c+2vE2DFf!Xn^rOLY@AqAKT zlOU*9FE?V;-!l8OhFzbzWItygvTl8Pf%?u|q*=3NM8od&gw(ctb>;uShtKz10ssI2 De!(H( literal 0 HcmV?d00001 diff --git a/assets/inter-roman-latin-ext.GZWE-KO4.woff2 b/assets/inter-roman-latin-ext.GZWE-KO4.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..715bd903b9b14d22a056f10e6d13b8d7e0acce57 GIT binary patch literal 59608 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

Coalesce

Designed to help you quickly build amazing web applications, Coalesce is a rapid-development, code generation-based web application framework created by IntelliTect and built on top of:

C#, .NET, and ASP.NET Core are the backend foundation of all Coalesce applications.
Design your relational data model with Entity Framework. Coalesce will use that data model to generate an extensible, customizable CRUD API that will drive both your custom pages and the out-of-the-box admin pages.
Frontend code for Coalesce apps is generated for and written in TypeScript, enabling discovery of Coalesce features through Intellisense and providing confidence that your code won't break as your application grows.
Build an awesome frontend application with Vue.js. Coalesce will generate TypeScript ViewModels to facilitate rapid development of custom pages.
Vite is the development and build tooling for your frontend Vue code, enabling lightning-fast single-page application development. Coalesce integrates ASP.NET Core and Vite together, streamlining local development to require nothing more than a dotnet run or a single-click launch in your IDE.

What do I do?

You are responsible for the interesting parts of your application:

  • Data Model
  • Business Logic
  • External Integrations
  • Page Content
  • Site Design
  • Custom Scripting

What is done for me?

Coalesce builds the part of your application that are mundane and monotonous to build:

  • Client side TypeScript ViewModels that mirror your data model for both lists and individual objects. Utilize these to rapidly build out your application's various pages.
  • APIs to interact with your models via endpoints like List, Get, Save, and more.
  • Out-of-the-box Vue Components for common controls like dates, selecting objects via drop downs, enums, etc. Dropdowns support searching and paging automatically.
  • A complete set of admin pages are provided, allowing you to read, create, edit, and delete data straight away without writing any additional code.

Getting Started

To get started with Coalesce, check out Getting Started with Vue.

While Knockout.js is still supported by Coalesce, it is a deprecated option and not recommended for new projects. If you do still want to choose Knockout, click here.

',12),h=[p];function u(m,g,f,_,w,b){return t(),o("div",null,h)}const C=e(c,[["render",u]]);export{v as __pageData,C as default}; diff --git a/assets/introduction.md.aI2TP5X5.lean.js b/assets/introduction.md.aI2TP5X5.lean.js new file mode 100644 index 000000000..1578d406f --- /dev/null +++ b/assets/introduction.md.aI2TP5X5.lean.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as o,R as a,a3 as i,a4 as r,a5 as n,a6 as s,a7 as l,a8 as d}from"./chunks/framework.g9eZ-ZSs.js";const v=JSON.parse('{"title":"Introduction","description":"Documentation home page for IntelliTect.Coalesce","frontmatter":{"lang":"en-US","title":"Introduction","description":"Documentation home page for IntelliTect.Coalesce"},"headers":[],"relativePath":"introduction.md","filePath":"introduction.md"}'),c={name:"introduction.md"},p=a("",12),h=[p];function u(m,g,f,_,w,b){return t(),o("div",null,h)}const C=e(c,[["render",u]]);export{v as __pageData,C as default}; diff --git a/assets/modeling_model-components_attributes.md.IUIXuOcX.js b/assets/modeling_model-components_attributes.md.IUIXuOcX.js new file mode 100644 index 000000000..5875a7987 --- /dev/null +++ b/assets/modeling_model-components_attributes.md.IUIXuOcX.js @@ -0,0 +1 @@ +import{_ as e,o as a,c as t,R as o}from"./chunks/framework.g9eZ-ZSs.js";const b=JSON.parse('{"title":"Attributes","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes.md","filePath":"modeling/model-components/attributes.md"}'),r={name:"modeling/model-components/attributes.md"},i=o('

Attributes

Coalesce provides a number of C# attributes that can be used to decorate your model classes and their properties in order to customize behavior, appearance, security, and more. Coalesce also supports a number of annotations from System.ComponentModel.DataAnnotations.

Coalesce Attributes

Browse the list in the sidebar to learn about the attributes that Coalesce provides that can be used to decorate your models.

ComponentModel Attributes

Coalesce also supports a number of the built-in System.ComponentModel.DataAnnotations attributes and will use these to shape the generated code.

[Display]

The displayed name and description of a property, as well as the order in which it appears in generated views, can be set via the [Display] attribute. By default, properties will be displayed in the order in which they are defined in their class.

[DisplayName]

The displayed name of a property can also be set via the [DisplayName] attribute.

[Required]

Properties with [Required] will generate client validation and server validation rules.

[Range]

Properties with [Range] will generate client validation and server validation rules.

[MinLength]

Properties with [MinLength] will generate client validation and server validation rules.

[MaxLength]

Properties with [MaxLength] will generate client validation and server validation rules.

[DataType]

Some values of DataType when provided to DataTypeAttribute on a string property will alter the behavior of the Vue Components. See c-display and See c-display for details.

[ForeignKey]

Normally, Coalesce figures out which properties are foreign keys, but if you don't use standard EF naming conventions then you'll need to annotate with [ForeignKey] to help out both EF and Coalesce. See the Entity Framework Relationships documentation for more.

[InverseProperty]

Sometimes, Coalesce (and EF, too) can have trouble figuring out what the foreign key is supposed to be for a collection navigation property. See the Entity Framework Relationships documentation for details on how and why to use [InverseProperty].

[DatabaseGenerated]

Primary Keys with [DatabaseGenerated(DatabaseGeneratedOption.None)] will be settable on the client and will be appropriately handled by the Standard Behaviors on the server. Unsupported on the Knockout front-end stack.

[NotMapped]

Model properties that aren't mapped to the database should be marked with [NotMapped] so that Coalesce doesn't try to load them from the database when searching or carrying out the Default Loading Behavior.

[DefaultValue]

Properties with [DefaultValue] will receive the specified value when a new ViewModel is instantiated on the client. This enables scenarios like pre-filling a required property with a suggested value.

',30),n=[i];function s(l,d,h,c,p,u){return a(),t("div",null,n)}const f=e(r,[["render",s]]);export{b as __pageData,f as default}; diff --git a/assets/modeling_model-components_attributes.md.IUIXuOcX.lean.js b/assets/modeling_model-components_attributes.md.IUIXuOcX.lean.js new file mode 100644 index 000000000..d624eedff --- /dev/null +++ b/assets/modeling_model-components_attributes.md.IUIXuOcX.lean.js @@ -0,0 +1 @@ +import{_ as e,o as a,c as t,R as o}from"./chunks/framework.g9eZ-ZSs.js";const b=JSON.parse('{"title":"Attributes","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes.md","filePath":"modeling/model-components/attributes.md"}'),r={name:"modeling/model-components/attributes.md"},i=o("",30),n=[i];function s(l,d,h,c,p,u){return a(),t("div",null,n)}const f=e(r,[["render",s]]);export{b as __pageData,f as default}; diff --git a/assets/modeling_model-components_attributes_client-validation.md.1GRmSFWa.js b/assets/modeling_model-components_attributes_client-validation.md.1GRmSFWa.js new file mode 100644 index 000000000..16741ae50 --- /dev/null +++ b/assets/modeling_model-components_attributes_client-validation.md.1GRmSFWa.js @@ -0,0 +1,31 @@ +import{_ as r,D as a,o as c,c as D,I as o,w as t,R as n,k as s,a as l}from"./chunks/framework.g9eZ-ZSs.js";const S=JSON.parse('{"title":"[ClientValidation]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/client-validation.md","filePath":"modeling/model-components/attributes/client-validation.md"}'),i={name:"modeling/model-components/attributes/client-validation.md"},y=n(`

[ClientValidation]

The [IntelliTect.Coalesce.DataAnnotations.ClientValidation] attribute is used to control the behavior of client-side model validation and to add additional client-only validation parameters. Database validation is available via standard System.ComponentModel.DataAnnotations annotations.

These propagate to the client as validations in TypeScript via generated Metadata and ViewModel rules (for Vue) or Knockout-Validation rules (for Knockout). For both stacks, any failing validation rules prevent saves from going to the server.

WARNING

This attribute controls client-side validation only. To perform server-side validation, create a custom Behaviors class for your types and/or place C# validation attributes on your models. Read More.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+
+    [ClientValidation(IsRequired = true, AllowSave = true)]
+    public string FirstName { get; set; }
+
+    [ClientValidation(IsRequired = true, AllowSave = false, MinLength = 1, MaxLength = 100)]
+    public string LastName { get; set; }
+}

Properties

Behavioral Properties

`,8),C=n('

If set to true, any client validation errors on the property will not prevent saving on the client. This includes all client-side validation, including null-checking for required foreign keys and other validations that are implicit. This also includes other explicit validation from System.ComponentModel.DataAnnotations annotations.

Instead, validation errors will be treated only as warnings, and will be available through the warnings: KnockoutValidationErrors property on the TypeScript ViewModel.

Note

Use AllowSave = true to allow partially complete data to still be saved, protecting your user from data loss upon navigation while still hinting to them that they are not done filling out data.

',3),d=s("p",null,"Set an error message to be used if any client validations fail",-1),u=s("h3",{id:"validation-rule-properties",tabindex:"-1"},[l("Validation Rule Properties "),s("a",{class:"header-anchor",href:"#validation-rule-properties","aria-label":'Permalink to "Validation Rule Properties"'},"​")],-1),h=s("p",null,[l("In addition to the following properties, you also customize validation on a per-instance basis of the "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html#viewmodels"},"ViewModels"),l(" using the "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html#rules-validation"},"Rules/Validation"),l(" methods.")],-1),b=s("div",{class:"language-c#"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"c#"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsRequired"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MinValue"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MaxValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MaxValue"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MinValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MinLength"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MaxValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MaxLength"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MinValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," Pattern"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsEmail"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsPhoneUs"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")])])])],-1),E=s("p",null,[l("The following attribute properties all map directly to "),s("a",{href:"https://github.com/Knockout-Contrib/Knockout-Validation/",target:"_blank",rel:"noreferrer"},"Knockout-Validation"),l(" properties.")],-1),g=s("div",{class:"language-c#"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"c#"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsRequired"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MinValue"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MaxValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MaxValue"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MinValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MinLength"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MaxValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MaxLength"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MinValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," Step"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," Pattern"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsEmail"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsPhoneUs"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsDate"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsDateIso"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsNumber"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsDigit"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")])])])],-1),F=s("p",null,"The following attribute properties are outputted to TypeScript unquoted. If you need to assert equality to a string, wrap the value you set to this property in quotes. Other literals (numerics, booleans, etc) need no wrapping.",-1),m=s("div",{class:"language-c#"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"c#"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," Equal"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," NotEqual"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")])])])],-1),v=s("p",null,[l("The following two properties may be used together to specify a custom "),s("a",{href:"https://github.com/Knockout-Contrib/Knockout-Validation/",target:"_blank",rel:"noreferrer"},"Knockout-Validation"),l(" property.")],-1),_=s("p",null,[l("It will be emitted into the TypeScript as "),s("code",null,"this.extend({ CustomName: CustomValue })"),l(". Neither value will be quoted in the emitted TypeScript - add quotes to your value as needed to generate valid TypeScript.")],-1),f=s("div",{class:"language-c#"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"c#"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," CustomName"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," CustomValue"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")])])])],-1);function k(V,T,w,I,M,x){const e=a("Prop"),p=a("CodeTabs");return c(),D("div",null,[y,o(e,{def:"public bool AllowSave { get; set; } // Knockout Only"}),C,o(e,{def:"public string ErrorMessage { get; set; }"}),d,u,o(p,null,{vue:t(()=>[h,b]),knockout:t(()=>[E,g,F,m,v,_,f]),_:1})])}const q=r(i,[["render",k]]);export{S as __pageData,q as default}; diff --git a/assets/modeling_model-components_attributes_client-validation.md.1GRmSFWa.lean.js b/assets/modeling_model-components_attributes_client-validation.md.1GRmSFWa.lean.js new file mode 100644 index 000000000..7e7bf1414 --- /dev/null +++ b/assets/modeling_model-components_attributes_client-validation.md.1GRmSFWa.lean.js @@ -0,0 +1,22 @@ +import{_ as r,D as a,o as c,c as D,I as o,w as t,R as n,k as s,a as l}from"./chunks/framework.g9eZ-ZSs.js";const S=JSON.parse('{"title":"[ClientValidation]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/client-validation.md","filePath":"modeling/model-components/attributes/client-validation.md"}'),i={name:"modeling/model-components/attributes/client-validation.md"},y=n("",8),C=n("",3),d=s("p",null,"Set an error message to be used if any client validations fail",-1),u=s("h3",{id:"validation-rule-properties",tabindex:"-1"},[l("Validation Rule Properties "),s("a",{class:"header-anchor",href:"#validation-rule-properties","aria-label":'Permalink to "Validation Rule Properties"'},"​")],-1),h=s("p",null,[l("In addition to the following properties, you also customize validation on a per-instance basis of the "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html#viewmodels"},"ViewModels"),l(" using the "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html#rules-validation"},"Rules/Validation"),l(" methods.")],-1),b=s("div",{class:"language-c#"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"c#"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsRequired"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MinValue"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MaxValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MaxValue"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MinValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MinLength"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MaxValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MaxLength"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MinValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," Pattern"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsEmail"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsPhoneUs"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")])])])],-1),E=s("p",null,[l("The following attribute properties all map directly to "),s("a",{href:"https://github.com/Knockout-Contrib/Knockout-Validation/",target:"_blank",rel:"noreferrer"},"Knockout-Validation"),l(" properties.")],-1),g=s("div",{class:"language-c#"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"c#"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsRequired"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MinValue"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MaxValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MaxValue"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MinValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MinLength"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MaxValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," MaxLength"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; } = "),s("span",{style:{color:"#569CD6"}},"double"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"MinValue"),s("span",{style:{color:"#D4D4D4"}},";")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," double"),s("span",{style:{color:"#9CDCFE"}}," Step"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," Pattern"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsEmail"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsPhoneUs"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsDate"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsDateIso"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsNumber"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," bool"),s("span",{style:{color:"#9CDCFE"}}," IsDigit"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")])])])],-1),F=s("p",null,"The following attribute properties are outputted to TypeScript unquoted. If you need to assert equality to a string, wrap the value you set to this property in quotes. Other literals (numerics, booleans, etc) need no wrapping.",-1),m=s("div",{class:"language-c#"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"c#"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," Equal"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," NotEqual"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")])])])],-1),v=s("p",null,[l("The following two properties may be used together to specify a custom "),s("a",{href:"https://github.com/Knockout-Contrib/Knockout-Validation/",target:"_blank",rel:"noreferrer"},"Knockout-Validation"),l(" property.")],-1),_=s("p",null,[l("It will be emitted into the TypeScript as "),s("code",null,"this.extend({ CustomName: CustomValue })"),l(". Neither value will be quoted in the emitted TypeScript - add quotes to your value as needed to generate valid TypeScript.")],-1),f=s("div",{class:"language-c#"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"c#"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," CustomName"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")]),l(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"public"),s("span",{style:{color:"#569CD6"}}," string"),s("span",{style:{color:"#9CDCFE"}}," CustomValue"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"get"),s("span",{style:{color:"#D4D4D4"}},"; "),s("span",{style:{color:"#9CDCFE"}},"set"),s("span",{style:{color:"#D4D4D4"}},"; }")])])])],-1);function k(V,T,w,I,M,x){const e=a("Prop"),p=a("CodeTabs");return c(),D("div",null,[y,o(e,{def:"public bool AllowSave { get; set; } // Knockout Only"}),C,o(e,{def:"public string ErrorMessage { get; set; }"}),d,u,o(p,null,{vue:t(()=>[h,b]),knockout:t(()=>[E,g,F,m,v,_,f]),_:1})])}const q=r(i,[["render",k]]);export{S as __pageData,q as default}; diff --git a/assets/modeling_model-components_attributes_coalesce.md.Lkb6B2Rs.js b/assets/modeling_model-components_attributes_coalesce.md.Lkb6B2Rs.js new file mode 100644 index 000000000..5b0575e1d --- /dev/null +++ b/assets/modeling_model-components_attributes_coalesce.md.Lkb6B2Rs.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as o,R as a}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"[Coalesce]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/coalesce.md","filePath":"modeling/model-components/attributes/coalesce.md"}'),s={name:"modeling/model-components/attributes/coalesce.md"},l=a('

[Coalesce]

Used to mark a type or member for generation by Coalesce.

Some types and members will be implicitly included in generation - for example, all types represented by a DbSet<T> on a DbContext that has a [Coalesce] attribute will automatically be included. Properties on these types will also be generated for unless explicitly excluded, since this is by far the most common usage scenario in Coalesce.

On the other hand, Methods on these types will not have endpoints generated unless they are explicitly annotated with [Coalesce] to avoid accidentally exposing methods that were perhaps not intended to be exposed.

The documentation pages for types and members that require/accept this attribute will state as such. An exhaustive list of all valid targets for [Coalesce] will not be found on this page.

',5),n=[l];function c(i,d,r,p,m,h){return t(),o("div",null,n)}const b=e(s,[["render",c]]);export{u as __pageData,b as default}; diff --git a/assets/modeling_model-components_attributes_coalesce.md.Lkb6B2Rs.lean.js b/assets/modeling_model-components_attributes_coalesce.md.Lkb6B2Rs.lean.js new file mode 100644 index 000000000..d7e738c40 --- /dev/null +++ b/assets/modeling_model-components_attributes_coalesce.md.Lkb6B2Rs.lean.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as o,R as a}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"[Coalesce]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/coalesce.md","filePath":"modeling/model-components/attributes/coalesce.md"}'),s={name:"modeling/model-components/attributes/coalesce.md"},l=a("",5),n=[l];function c(i,d,r,p,m,h){return t(),o("div",null,n)}const b=e(s,[["render",c]]);export{u as __pageData,b as default}; diff --git a/assets/modeling_model-components_attributes_controller-action.md.Ldygq9AT.js b/assets/modeling_model-components_attributes_controller-action.md.Ldygq9AT.js new file mode 100644 index 000000000..13b1b3a99 --- /dev/null +++ b/assets/modeling_model-components_attributes_controller-action.md.Ldygq9AT.js @@ -0,0 +1,27 @@ +import{_ as o,D as e,o as l,c as p,I as a,R as s}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"[ControllerAction]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/controller-action.md","filePath":"modeling/model-components/attributes/controller-action.md"}'),t={name:"modeling/model-components/attributes/controller-action.md"},r=s(`

[ControllerAction]

Specifies how a custom method is exposed via HTTP. Can be used to customize the HTTP method/verb for the method, as well as caching behavior.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    public string LastName { get; set; }
+
+    public string PictureHash { get; set; }
+
+    [Coalesce]
+    [ControllerAction(Method = HttpMethod.Get)]
+    public static long PersonCount(AppDbContext db, string lastNameStartsWith = "")
+    {
+        return db.People.Count(f => f.LastName.StartsWith(lastNameStartsWith));
+    }
+
+    [Coalesce]
+    [ControllerAction(HttpMethod.Get, VaryByProperty = nameof(PictureHash))]
+    public IFile GetPicture(AppDbContext db)
+    {
+        return new IntelliTect.Coalesce.Models.File(db.PersonPictures
+            .Where(x => x.PersonId == this.PersonId)
+            .Select(x => x.Content)
+        )
+        {
+            ContentType = "image/jpg",
+        };
+    }
+}

Properties

`,5),c=s("

The HTTP method to use on the generated API Controller.

Enum values are:

  • HttpMethod.Post Use the POST method.
  • HttpMethod.Get Use the GET method.
  • HttpMethod.Put Use the PUT method.
  • HttpMethod.Delete Use the DELETE method.
  • HttpMethod.Patch Use the PATCH method.
",3),D=s('

For HTTP GET model instance methods, if VaryByProperty is set to the name of a property on the parent model class, ETag headers based on the value of this property will be used to implement caching. If the client provides a matching If-None-Match Header with the request, the method will not be invoked and HTTP Status `304 Not Modified`` will be returned.

Additionally, if the VaryByProperty is set to a client-exposed property, the value of the property will be included in the query string when performing API calls to invoke the method. If the query string value matches the current value on the model, a long-term Cache-Control header will be set on the response, allowing the client to avoid making future invocations to the same method while the value of the VaryByProperty remains the same.

',2);function y(i,C,d,h,m,u){const n=e("Prop");return l(),p("div",null,[r,a(n,{def:"public HttpMethod Method { get; set; } = HttpMethod.Post;",ctor:"1"}),c,a(n,{def:"public string VaryByProperty { get; set; }"}),D])}const P=o(t,[["render",y]]);export{_ as __pageData,P as default}; diff --git a/assets/modeling_model-components_attributes_controller-action.md.Ldygq9AT.lean.js b/assets/modeling_model-components_attributes_controller-action.md.Ldygq9AT.lean.js new file mode 100644 index 000000000..fb18a775c --- /dev/null +++ b/assets/modeling_model-components_attributes_controller-action.md.Ldygq9AT.lean.js @@ -0,0 +1 @@ +import{_ as o,D as e,o as l,c as p,I as a,R as s}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"[ControllerAction]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/controller-action.md","filePath":"modeling/model-components/attributes/controller-action.md"}'),t={name:"modeling/model-components/attributes/controller-action.md"},r=s("",5),c=s("",3),D=s("",2);function y(i,C,d,h,m,u){const n=e("Prop");return l(),p("div",null,[r,a(n,{def:"public HttpMethod Method { get; set; } = HttpMethod.Post;",ctor:"1"}),c,a(n,{def:"public string VaryByProperty { get; set; }"}),D])}const P=o(t,[["render",y]]);export{_ as __pageData,P as default}; diff --git a/assets/modeling_model-components_attributes_controller.md.4MsqYLok.js b/assets/modeling_model-components_attributes_controller.md.4MsqYLok.js new file mode 100644 index 000000000..889ce1b2e --- /dev/null +++ b/assets/modeling_model-components_attributes_controller.md.4MsqYLok.js @@ -0,0 +1,7 @@ +import{_ as n,D as a,o as r,c,I as t,R as s,k as e,a as l}from"./chunks/framework.g9eZ-ZSs.js";const I=JSON.parse('{"title":"[Controller]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/controller.md","filePath":"modeling/model-components/attributes/controller.md"}'),p={name:"modeling/model-components/attributes/controller.md"},i=s(`

[Controller]

Allows for control over the generated MVC Controllers.

Currently only controls over the API controllers are present, but additional properties may be added in the future.

This attribute may be placed on any type from which an API controller is generated, including Entity Models, Custom DTOs, and Services.

Example Usage

c#
[Controller(ApiRouted = false, ApiControllerSuffix = "Gen", ApiActionsProtected = true)]
+public class Person
+{
+    public int PersonId { get; set; }
+    
+    ...
+}

Properties

`,7),d=s("

Determines whether or not a [Route] annotation will be placed on the generated API controller. Set to false to prevent emission of the [Route] attribute.

Use cases include:

  • Defining your routes through IRouteBuilder in Startup.cs instead
  • Preventing API controllers from being exposed by default.
  • Routing to your own custom controller that inherits from the generated API controller in order to implement more granular or complex authorization logic.
",3),u=e("p",null,"If set, will determine the name of the generated API controller.",-1),h=e("p",null,[l("Takes precedence over the value of "),e("code",null,"ApiControllerSuffix"),l(".")],-1),m=e("p",null,"If set, will be appended to the default name of the API controller generated for this model.",-1),D=e("p",null,[l("Will be overridden by the value of "),e("code",null,"ApiControllerName"),l(" if it is set.")],-1),_=s('

If true, actions on the generated API controller will have an access modifier of protected instead of public.

In order to consume the generated API controller, you must inherit from the generated controller and override each desired generated action method via hiding (i.e. use public new ..., not public override ...).

Note

If you inherit from the generated API controllers and override their methods without setting ApiActionsProtected = true, all non-overridden actions from the generated controller will still be exposed as normal.

',3);function f(y,C,g,b,A,P){const o=a("Prop");return r(),c("div",null,[i,t(o,{def:"public bool ApiRouted { get; set; } = true;"}),d,t(o,{def:"public string ApiControllerName { get; set; } = null;"}),u,h,t(o,{def:"public string ApiControllerSuffix { get; set; } = null;"}),m,D,t(o,{def:"public bool ApiActionsProtected { get; set; } = false;"}),_])}const T=n(p,[["render",f]]);export{I as __pageData,T as default}; diff --git a/assets/modeling_model-components_attributes_controller.md.4MsqYLok.lean.js b/assets/modeling_model-components_attributes_controller.md.4MsqYLok.lean.js new file mode 100644 index 000000000..736261d86 --- /dev/null +++ b/assets/modeling_model-components_attributes_controller.md.4MsqYLok.lean.js @@ -0,0 +1 @@ +import{_ as n,D as a,o as r,c,I as t,R as s,k as e,a as l}from"./chunks/framework.g9eZ-ZSs.js";const I=JSON.parse('{"title":"[Controller]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/controller.md","filePath":"modeling/model-components/attributes/controller.md"}'),p={name:"modeling/model-components/attributes/controller.md"},i=s("",7),d=s("",3),u=e("p",null,"If set, will determine the name of the generated API controller.",-1),h=e("p",null,[l("Takes precedence over the value of "),e("code",null,"ApiControllerSuffix"),l(".")],-1),m=e("p",null,"If set, will be appended to the default name of the API controller generated for this model.",-1),D=e("p",null,[l("Will be overridden by the value of "),e("code",null,"ApiControllerName"),l(" if it is set.")],-1),_=s("",3);function f(y,C,g,b,A,P){const o=a("Prop");return r(),c("div",null,[i,t(o,{def:"public bool ApiRouted { get; set; } = true;"}),d,t(o,{def:"public string ApiControllerName { get; set; } = null;"}),u,h,t(o,{def:"public string ApiControllerSuffix { get; set; } = null;"}),m,D,t(o,{def:"public bool ApiActionsProtected { get; set; } = false;"}),_])}const T=n(p,[["render",f]]);export{I as __pageData,T as default}; diff --git a/assets/modeling_model-components_attributes_create-controller.md.R_UFOdqz.js b/assets/modeling_model-components_attributes_create-controller.md.R_UFOdqz.js new file mode 100644 index 000000000..4358593f3 --- /dev/null +++ b/assets/modeling_model-components_attributes_create-controller.md.R_UFOdqz.js @@ -0,0 +1,7 @@ +import{_ as a,D as o,o as l,c as n,I as s,R as t}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"[CreateController]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/create-controller.md","filePath":"modeling/model-components/attributes/create-controller.md"}'),r={name:"modeling/model-components/attributes/create-controller.md"},p=t(`

[CreateController]

By default an API and View controller are both created. This allows for suppressing the creation of either or both of these.

Example Usage

c#
[CreateController(view: false, api: true)]
+public class Person
+{
+    public int PersonId { get; set; }
+    
+    ...
+}

Properties

`,5);function c(i,D,d,C,u,y){const e=o("Prop");return l(),n("div",null,[p,s(e,{def:"public bool WillCreateView { get; set; } = true",ctor:"1"}),s(e,{def:"public bool WillCreateApi { get; set; } = true",ctor:"2"})])}const _=a(r,[["render",c]]);export{m as __pageData,_ as default}; diff --git a/assets/modeling_model-components_attributes_create-controller.md.R_UFOdqz.lean.js b/assets/modeling_model-components_attributes_create-controller.md.R_UFOdqz.lean.js new file mode 100644 index 000000000..cf8a9476a --- /dev/null +++ b/assets/modeling_model-components_attributes_create-controller.md.R_UFOdqz.lean.js @@ -0,0 +1 @@ +import{_ as a,D as o,o as l,c as n,I as s,R as t}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"[CreateController]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/create-controller.md","filePath":"modeling/model-components/attributes/create-controller.md"}'),r={name:"modeling/model-components/attributes/create-controller.md"},p=t("",5);function c(i,D,d,C,u,y){const e=o("Prop");return l(),n("div",null,[p,s(e,{def:"public bool WillCreateView { get; set; } = true",ctor:"1"}),s(e,{def:"public bool WillCreateApi { get; set; } = true",ctor:"2"})])}const _=a(r,[["render",c]]);export{m as __pageData,_ as default}; diff --git a/assets/modeling_model-components_attributes_date-type.md.IriIMwEk.js b/assets/modeling_model-components_attributes_date-type.md.IriIMwEk.js new file mode 100644 index 000000000..8074565b8 --- /dev/null +++ b/assets/modeling_model-components_attributes_date-type.md.IriIMwEk.js @@ -0,0 +1,7 @@ +import{_ as t,D as n,o,c as p,I as l,a,R as r,k as e}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse('{"title":"[DateType]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/date-type.md","filePath":"modeling/model-components/attributes/date-type.md"}'),c={name:"modeling/model-components/attributes/date-type.md"},D=r(`

[DateType]

Specifies whether a DateTime type will have a date and a time, or only a date.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+
+    [DateType(DateTypeAttribute.DateTypes.DateOnly)]
+    public DateTimeOffset? BirthDate { get; set; }
+}

Properties

`,5),i=e("p",null,"The type of date the property represents.",-1),y=e("p",null,"Enum values are:",-1),d=e("ul",null,[e("li",null,[e("code",null,"DateTypeAttribute.DateTypes.DateTime"),a(" Subject is both a date and time.")]),e("li",null,[e("code",null,"DateTypeAttribute.DateTypes.DateOnly"),a(" Subject is only a date with no significant time component.")])],-1);function u(m,_,h,C,T,b){const s=n("Prop");return o(),p("div",null,[D,l(s,{def:"public DateTypes DateType { get; set; } = DateTypes.DateTime; ",ctor:"1"}),a(),i,y,d])}const E=t(c,[["render",u]]);export{g as __pageData,E as default}; diff --git a/assets/modeling_model-components_attributes_date-type.md.IriIMwEk.lean.js b/assets/modeling_model-components_attributes_date-type.md.IriIMwEk.lean.js new file mode 100644 index 000000000..c585e6a22 --- /dev/null +++ b/assets/modeling_model-components_attributes_date-type.md.IriIMwEk.lean.js @@ -0,0 +1 @@ +import{_ as t,D as n,o,c as p,I as l,a,R as r,k as e}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse('{"title":"[DateType]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/date-type.md","filePath":"modeling/model-components/attributes/date-type.md"}'),c={name:"modeling/model-components/attributes/date-type.md"},D=r("",5),i=e("p",null,"The type of date the property represents.",-1),y=e("p",null,"Enum values are:",-1),d=e("ul",null,[e("li",null,[e("code",null,"DateTypeAttribute.DateTypes.DateTime"),a(" Subject is both a date and time.")]),e("li",null,[e("code",null,"DateTypeAttribute.DateTypes.DateOnly"),a(" Subject is only a date with no significant time component.")])],-1);function u(m,_,h,C,T,b){const s=n("Prop");return o(),p("div",null,[D,l(s,{def:"public DateTypes DateType { get; set; } = DateTypes.DateTime; ",ctor:"1"}),a(),i,y,d])}const E=t(c,[["render",u]]);export{g as __pageData,E as default}; diff --git a/assets/modeling_model-components_attributes_default-order-by.md.2t7Z9gZk.js b/assets/modeling_model-components_attributes_default-order-by.md.2t7Z9gZk.js new file mode 100644 index 000000000..83d4bcc98 --- /dev/null +++ b/assets/modeling_model-components_attributes_default-order-by.md.2t7Z9gZk.js @@ -0,0 +1,23 @@ +import{_ as l,D as o,o as p,c as t,I as n,a as e,R as r,k as s}from"./chunks/framework.g9eZ-ZSs.js";const O=JSON.parse('{"title":"[DefaultOrderBy]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/default-order-by.md","filePath":"modeling/model-components/attributes/default-order-by.md"}'),c={name:"modeling/model-components/attributes/default-order-by.md"},D=r(`

[DefaultOrderBy]

Allows setting of the default manner in which the data returned to the client will be sorted. Multiple fields can be used to sort an object by specifying an index.

This affects the sort order both when requesting a list of the model itself, as well as when the model appears as a child collection off of a navigation property of another object.

In the first case (a list of the model itself), this can be overridden by setting the orderBy or orderByDescending property on the TypeScript ListViewModel - see TypeScript List ViewModels.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    
+    public int DepartmentId { get; set; }
+
+    [DefaultOrderBy(FieldOrder = 0, FieldName = nameof(Department.Order))]
+    public Department Department { get; set; }
+    
+    [DefaultOrderBy(FieldOrder = 1)]
+    public string LastName { get; set; }
+}
c#
public class Person
+{
+    public int PersonId { get; set; }
+    
+    public int DepartmentId { get; set; }
+
+    [DefaultOrderBy(FieldOrder = 0, FieldName = nameof(Department.Order))]
+    public Department Department { get; set; }
+    
+    [DefaultOrderBy(FieldOrder = 1)]
+    public string LastName { get; set; }
+}

Properties

`,8),i=s("p",null,"Specify the index of this field when sorting by multiple fields.",-1),y=s("p",null,[e("Lower-valued properties will be used first; higher-valued properties will be used as a tiebreaker (i.e. "),s("code",null,".ThenBy(...)"),e(").")],-1),d=s("p",null,"Specify the direction of the ordering for the property.",-1),C=s("p",null,"Enum values are:",-1),u=s("ul",null,[s("li",null,[s("code",null,"DefaultOrderByAttribute.OrderByDirections.Ascending")]),s("li",null,[s("code",null,"DefaultOrderByAttribute.OrderByDirections.Descending")])],-1),h=s("p",null,[e("When using the "),s("code",null,"DefaultOrderByAttribute"),e(" on an object property, specifies the field on the object to use for sorting. See the first example above.")],-1);function f(m,b,g,_,E,B){const a=o("Prop");return p(),t("div",null,[D,n(a,{def:"public int FieldOrder { get; set; } = 0; ",ctor:"1"}),e(),i,y,n(a,{def:"public OrderByDirections OrderByDirection { get; set; } = OrderByDirections.Ascending;",ctor:"2"}),d,C,u,n(a,{def:"public string FieldName { get; set; }"}),h])}const v=l(c,[["render",f]]);export{O as __pageData,v as default}; diff --git a/assets/modeling_model-components_attributes_default-order-by.md.2t7Z9gZk.lean.js b/assets/modeling_model-components_attributes_default-order-by.md.2t7Z9gZk.lean.js new file mode 100644 index 000000000..32211e9e8 --- /dev/null +++ b/assets/modeling_model-components_attributes_default-order-by.md.2t7Z9gZk.lean.js @@ -0,0 +1 @@ +import{_ as l,D as o,o as p,c as t,I as n,a as e,R as r,k as s}from"./chunks/framework.g9eZ-ZSs.js";const O=JSON.parse('{"title":"[DefaultOrderBy]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/default-order-by.md","filePath":"modeling/model-components/attributes/default-order-by.md"}'),c={name:"modeling/model-components/attributes/default-order-by.md"},D=r("",8),i=s("p",null,"Specify the index of this field when sorting by multiple fields.",-1),y=s("p",null,[e("Lower-valued properties will be used first; higher-valued properties will be used as a tiebreaker (i.e. "),s("code",null,".ThenBy(...)"),e(").")],-1),d=s("p",null,"Specify the direction of the ordering for the property.",-1),C=s("p",null,"Enum values are:",-1),u=s("ul",null,[s("li",null,[s("code",null,"DefaultOrderByAttribute.OrderByDirections.Ascending")]),s("li",null,[s("code",null,"DefaultOrderByAttribute.OrderByDirections.Descending")])],-1),h=s("p",null,[e("When using the "),s("code",null,"DefaultOrderByAttribute"),e(" on an object property, specifies the field on the object to use for sorting. See the first example above.")],-1);function f(m,b,g,_,E,B){const a=o("Prop");return p(),t("div",null,[D,n(a,{def:"public int FieldOrder { get; set; } = 0; ",ctor:"1"}),e(),i,y,n(a,{def:"public OrderByDirections OrderByDirection { get; set; } = OrderByDirections.Ascending;",ctor:"2"}),d,C,u,n(a,{def:"public string FieldName { get; set; }"}),h])}const v=l(c,[["render",f]]);export{O as __pageData,v as default}; diff --git a/assets/modeling_model-components_attributes_dto-includes-excludes.md.gWiALlZf.js b/assets/modeling_model-components_attributes_dto-includes-excludes.md.gWiALlZf.js new file mode 100644 index 000000000..da534569d --- /dev/null +++ b/assets/modeling_model-components_attributes_dto-includes-excludes.md.gWiALlZf.js @@ -0,0 +1,40 @@ +import{_ as c,D as o,o as r,c as D,I as l,w as n,a as e,R as a,k as s}from"./chunks/framework.g9eZ-ZSs.js";const F=JSON.parse('{"title":"[DtoIncludes] & [DtoExcludes]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/dto-includes-excludes.md","filePath":"modeling/model-components/attributes/dto-includes-excludes.md"}'),i={name:"modeling/model-components/attributes/dto-includes-excludes.md"},d=a(`

[DtoIncludes] & [DtoExcludes]

Allows for easily controlling what data gets set to the client. When requesting data from the generated client-side list view models, you can specify an includes property on the ViewModel or ListViewModel.

For more information about the includes string, see Includes String.

When the database entries are returned to the client they will be trimmed based on the requested includes string and the values in DtoExcludes and DtoIncludes.

DANGER

These attributes are not security attributes - consumers of your application's API can set the includes string to any value when making a request.

Do not use them to keep certain data private - use the Security Attributes family of attributes for that.

It is important to note that the value of the includes string will match against these attributes on any of your models that appears in the object graph being mapped to DTOs - it is not limited only to the model type of the root object.

Important

DtoIncludes does not ensure that specific data will be loaded from the database. It only permits what is already loaded into the current EF DbContext to be returned from the API. See Data Sources to learn how to control what data gets loaded from the database.

Example Usage

Server code:

c#
public class Person
+{
+    // Don't include CreatedBy when editing - will be included for all other views
+    [DtoExcludes("Editor")]
+    public AppUser CreatedBy { get; set; }
+
+    // Only include the Person's Department when \`includes == "details"\` on the TypeScript ViewModel.
+    [DtoIncludes("details")]
+    public Department Department { get; set; }
+
+    // LastName will be included in all views
+    public string LastName { get; set; }
+}
+
+public class Department
+{
+    [DtoIncludes("details")]
+    public ICollection<Person> People { get; set; }
+}

Client code:

`,11),y=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList.$items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList2.items will be allowed to contain both CreatedBy and Department objects. ")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Department will be allowed to include its other Person objects.")])])])],-1),C=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}}," // objects in personList.items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}}," // objects in personList2.items will be allowed to contain both CreatedBy and Department objects. Department will be allowed to include its other Person objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")])])])],-1),u=s("h2",{id:"properties",tabindex:"-1"},[e("Properties "),s("a",{class:"header-anchor",href:"#properties","aria-label":'Permalink to "Properties"'},"​")],-1),h=a('

A comma-delimited list of values of includes on which to operate.

For DtoIncludes, this will be the values of includes for which this property will be allowed to be serialized and sent to the client.

For DtoExcludes, this will be the values of includes for which this property will not be serialized and sent to the client.

',3);function m(b,E,w,g,_,f){const t=o("CodeTabs"),p=o("Prop");return r(),D("div",null,[d,l(t,null,{vue:n(()=>[y]),knockout:n(()=>[C]),_:1}),u,l(p,{def:"public string ContentViews { get; set; }",ctor:"1"}),e(),h])}const v=c(i,[["render",m]]);export{F as __pageData,v as default}; diff --git a/assets/modeling_model-components_attributes_dto-includes-excludes.md.gWiALlZf.lean.js b/assets/modeling_model-components_attributes_dto-includes-excludes.md.gWiALlZf.lean.js new file mode 100644 index 000000000..080a34555 --- /dev/null +++ b/assets/modeling_model-components_attributes_dto-includes-excludes.md.gWiALlZf.lean.js @@ -0,0 +1,22 @@ +import{_ as c,D as o,o as r,c as D,I as l,w as n,a as e,R as a,k as s}from"./chunks/framework.g9eZ-ZSs.js";const F=JSON.parse('{"title":"[DtoIncludes] & [DtoExcludes]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/dto-includes-excludes.md","filePath":"modeling/model-components/attributes/dto-includes-excludes.md"}'),i={name:"modeling/model-components/attributes/dto-includes-excludes.md"},d=a("",11),y=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList.$items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Objects in personList2.items will be allowed to contain both CreatedBy and Department objects. ")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Department will be allowed to include its other Person objects.")])])])],-1),C=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Editor"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}}," // objects in personList.items will not contain CreatedBy nor Department objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," personList2"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"includes"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"details"'),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"personList2"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"(() "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}}," // objects in personList2.items will be allowed to contain both CreatedBy and Department objects. Department will be allowed to include its other Person objects.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")])])])],-1),u=s("h2",{id:"properties",tabindex:"-1"},[e("Properties "),s("a",{class:"header-anchor",href:"#properties","aria-label":'Permalink to "Properties"'},"​")],-1),h=a("",3);function m(b,E,w,g,_,f){const t=o("CodeTabs"),p=o("Prop");return r(),D("div",null,[d,l(t,null,{vue:n(()=>[y]),knockout:n(()=>[C]),_:1}),u,l(p,{def:"public string ContentViews { get; set; }",ctor:"1"}),e(),h])}const v=c(i,[["render",m]]);export{F as __pageData,v as default}; diff --git a/assets/modeling_model-components_attributes_execute.md.OmkAEPLi.js b/assets/modeling_model-components_attributes_execute.md.OmkAEPLi.js new file mode 100644 index 000000000..ae29345eb --- /dev/null +++ b/assets/modeling_model-components_attributes_execute.md.OmkAEPLi.js @@ -0,0 +1,11 @@ +import{_ as l,D as n,o as r,c,I as o,R as t,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const P=JSON.parse('{"title":"[Execute]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/execute.md","filePath":"modeling/model-components/attributes/execute.md"}'),i={name:"modeling/model-components/attributes/execute.md"},p=t(`

[Execute]

Controls permissions for executing of a static or instance method through the API.

For other security controls, see Security Attributes.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    
+    [Coalesce, Execute(Roles = "Payroll,HR")]
+    public void GiveRaise(int centsPerHour) {
+        ...
+    }
+
+    ...
+}

Properties

`,6),d=e("p",null,"A comma-separated list of roles which are allowed to execute the method.",-1),u=t("

The level of access to allow for the action for the method.

Enum values are:

  • SecurityPermissionLevels.AllowAll Allow all users to perform the action for the attribute, including users who are not authenticated at all.
  • SecurityPermissionLevels.AllowAuthorized Allow only users who are members of the roles specified on the attribute to perform the action. If no roles are specified on the attribute, then all authenticated users are allowed (no anonymous access).
  • SecurityPermissionLevels.DenyAll Deny the action to all users, regardless of authentication status or authorization level. If DenyAll is used, no API endpoint for the action will be generated.
",3),h=e("p",null,"If true, the method's arguments will be cleared after a successful invocation on admin pages.",-1),D=e("p",null,[a("If non-null, overrides the value of "),e("a",{href:"/Coalesce/topics/security.html#attribute-validation"},[e("code",null,"CoalesceOptions.ValidateAttributesForMethods")]),a(" when determining whether to perform automatic server-side validation of the method's parameters.")],-1),m=e("p",null,"If validation is performed, the method's parameters will be validated by the server and the method invocation prevented if errors are found.",-1);function y(_,f,b,C,v,g){const s=n("Prop");return r(),c("div",null,[p,o(s,{def:"public string Roles { get; set; }"}),d,o(s,{def:"public SecurityPermissionLevels PermissionLevel { get; set; } = SecurityPermissionLevels.AllowAuthorized;"}),u,o(s,{def:"public bool AutoClear { get; set; }"}),h,o(s,{def:"public bool? ValidateAttributes { get; set; }"}),D,m])}const x=l(i,[["render",y]]);export{P as __pageData,x as default}; diff --git a/assets/modeling_model-components_attributes_execute.md.OmkAEPLi.lean.js b/assets/modeling_model-components_attributes_execute.md.OmkAEPLi.lean.js new file mode 100644 index 000000000..ea7826a35 --- /dev/null +++ b/assets/modeling_model-components_attributes_execute.md.OmkAEPLi.lean.js @@ -0,0 +1 @@ +import{_ as l,D as n,o as r,c,I as o,R as t,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const P=JSON.parse('{"title":"[Execute]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/execute.md","filePath":"modeling/model-components/attributes/execute.md"}'),i={name:"modeling/model-components/attributes/execute.md"},p=t("",6),d=e("p",null,"A comma-separated list of roles which are allowed to execute the method.",-1),u=t("",3),h=e("p",null,"If true, the method's arguments will be cleared after a successful invocation on admin pages.",-1),D=e("p",null,[a("If non-null, overrides the value of "),e("a",{href:"/Coalesce/topics/security.html#attribute-validation"},[e("code",null,"CoalesceOptions.ValidateAttributesForMethods")]),a(" when determining whether to perform automatic server-side validation of the method's parameters.")],-1),m=e("p",null,"If validation is performed, the method's parameters will be validated by the server and the method invocation prevented if errors are found.",-1);function y(_,f,b,C,v,g){const s=n("Prop");return r(),c("div",null,[p,o(s,{def:"public string Roles { get; set; }"}),d,o(s,{def:"public SecurityPermissionLevels PermissionLevel { get; set; } = SecurityPermissionLevels.AllowAuthorized;"}),u,o(s,{def:"public bool AutoClear { get; set; }"}),h,o(s,{def:"public bool? ValidateAttributes { get; set; }"}),D,m])}const x=l(i,[["render",y]]);export{P as __pageData,x as default}; diff --git a/assets/modeling_model-components_attributes_hidden.md.MV6N1IoA.js b/assets/modeling_model-components_attributes_hidden.md.MV6N1IoA.js new file mode 100644 index 000000000..5e6d51a19 --- /dev/null +++ b/assets/modeling_model-components_attributes_hidden.md.MV6N1IoA.js @@ -0,0 +1,7 @@ +import{_ as a,D as t,o as n,c as o,I as l,R as e}from"./chunks/framework.g9eZ-ZSs.js";const b=JSON.parse('{"title":"[Hidden]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/hidden.md","filePath":"modeling/model-components/attributes/hidden.md"}'),r={name:"modeling/model-components/attributes/hidden.md"},i=e(`

[Hidden]

Mark an property as hidden from the edit, List or All areas.

DANGER

This attribute is not a security attribute - it only affects the rendering of the admin pages. It has no impact on data visibility in the API.

Do not use it to keep certain data private - use the Security Attributes family of attributes for that.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+
+    [Hidden(HiddenAttribute.Areas.All)]
+    public int? IncomeLevelId { get; set; }
+}

Properties

`,6),p=e("

The areas in which the property should be hidden.

Enum values are:

  • HiddenAttribute.Areas.None Hide from no generated views. Primary and Foreign keys are hidden by default - setting this value explicitly can override this default behavior.
  • HiddenAttribute.Areas.All Hide from all generated views
  • HiddenAttribute.Areas.List Hide from generated list views only (Knockout Table/Cards, Vue c-admin-table)
  • HiddenAttribute.Areas.Edit Hide from generated editor only (Knockout CreateEdit, Vue c-admin-editor)
",3);function c(d,D,u,h,y,m){const s=t("Prop");return n(),o("div",null,[i,l(s,{def:"public Areas Area { get; set; } = Areas.All;",ctor:"1"}),p])}const C=a(r,[["render",c]]);export{b as __pageData,C as default}; diff --git a/assets/modeling_model-components_attributes_hidden.md.MV6N1IoA.lean.js b/assets/modeling_model-components_attributes_hidden.md.MV6N1IoA.lean.js new file mode 100644 index 000000000..b0e46ecef --- /dev/null +++ b/assets/modeling_model-components_attributes_hidden.md.MV6N1IoA.lean.js @@ -0,0 +1 @@ +import{_ as a,D as t,o as n,c as o,I as l,R as e}from"./chunks/framework.g9eZ-ZSs.js";const b=JSON.parse('{"title":"[Hidden]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/hidden.md","filePath":"modeling/model-components/attributes/hidden.md"}'),r={name:"modeling/model-components/attributes/hidden.md"},i=e("",6),p=e("",3);function c(d,D,u,h,y,m){const s=t("Prop");return n(),o("div",null,[i,l(s,{def:"public Areas Area { get; set; } = Areas.All;",ctor:"1"}),p])}const C=a(r,[["render",c]]);export{b as __pageData,C as default}; diff --git a/assets/modeling_model-components_attributes_inject.md.wUXXw8WO.js b/assets/modeling_model-components_attributes_inject.md.wUXXw8WO.js new file mode 100644 index 000000000..0f39ade5b --- /dev/null +++ b/assets/modeling_model-components_attributes_inject.md.wUXXw8WO.js @@ -0,0 +1,12 @@ +import{_ as s,o as a,c as n,R as e}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"[Inject]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/inject.md","filePath":"modeling/model-components/attributes/inject.md"}'),o={name:"modeling/model-components/attributes/inject.md"},l=e(`

[Inject]

Used to mark a Method parameter for dependency injection from the application's IServiceProvider.

See Methods for more.

This gets translated to a Microsoft.AspNetCore.Mvc.FromServicesAttribute in the generated API controller's action.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    public string FirstName { get; set; }
+    public string LastName { get; set; }
+
+    public string GetFullName([Inject] ILogger<Person> logger)
+    {
+        logger.LogInformation("Person " + PersonId + "'s full name was requested");
+        return FirstName + " " + LastName";
+    }
+}
`,6),p=[l];function t(c,r,D,i,y,d){return a(),n("div",null,p)}const u=s(o,[["render",t]]);export{m as __pageData,u as default}; diff --git a/assets/modeling_model-components_attributes_inject.md.wUXXw8WO.lean.js b/assets/modeling_model-components_attributes_inject.md.wUXXw8WO.lean.js new file mode 100644 index 000000000..0935d1383 --- /dev/null +++ b/assets/modeling_model-components_attributes_inject.md.wUXXw8WO.lean.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as n,R as e}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"[Inject]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/inject.md","filePath":"modeling/model-components/attributes/inject.md"}'),o={name:"modeling/model-components/attributes/inject.md"},l=e("",6),p=[l];function t(c,r,D,i,y,d){return a(),n("div",null,p)}const u=s(o,[["render",t]]);export{m as __pageData,u as default}; diff --git a/assets/modeling_model-components_attributes_internal-use.md.J0q2AD4y.js b/assets/modeling_model-components_attributes_internal-use.md.J0q2AD4y.js new file mode 100644 index 000000000..068a6c0b4 --- /dev/null +++ b/assets/modeling_model-components_attributes_internal-use.md.J0q2AD4y.js @@ -0,0 +1,20 @@ +import{_ as s,o as a,c as n,R as e}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"[InternalUse]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/internal-use.md","filePath":"modeling/model-components/attributes/internal-use.md"}'),l={name:"modeling/model-components/attributes/internal-use.md"},o=e(`

[InternalUse]

Used to mark a type, property or method for internal use. Internal Use members are:

  • Not exposed via the API.
  • Not present in the generated TypeScript view models.
  • Not present nor accounted for in the generated C# DTOs.
  • Not present in the generated editor or list views.

Effectively, an Internal Use member is invisible to Coalesce. This attribute can be considered a Security Attribute.

Note that this only needs to be used on members that are public. Non-public members (including internal) are always invisible to Coalesce.

Example Usage

In this example, Color is the property exposed to the API, but ColorHex is the property that maps to the database that stores the value. A helper method also exists for the color generation, but needs no attribute to be hidden since methods must be explicitly exposed with [Coalesce].

If no color is saved in the database (the user hasn't picked a color), one is deterministically created.

c#
public class ApplicationUser
+{
+    public int ApplicationUserId { get; set; }
+
+    [InternalUse]
+    public string ColorHex { get; set; }
+
+    [NotMapped]
+    public string Color
+    {
+        get => ColorHex ?? GenerateColor(ApplicationUserId).ToRGBHexString();
+        set => ColorHex = value;
+    }
+
+    public static HSLColor GenerateColor(int? seed = null)
+    {
+        var random = seed.HasValue ? new Random(seed.Value) : new Random();
+        return new HSLColor(random.NextDouble(), random.Next(40, 100) / 100d, random.Next(25, 65) / 100d);
+    }
+}
`,9),p=[o];function t(r,c,D,i,y,C){return a(),n("div",null,p)}const u=s(l,[["render",t]]);export{m as __pageData,u as default}; diff --git a/assets/modeling_model-components_attributes_internal-use.md.J0q2AD4y.lean.js b/assets/modeling_model-components_attributes_internal-use.md.J0q2AD4y.lean.js new file mode 100644 index 000000000..e19b08750 --- /dev/null +++ b/assets/modeling_model-components_attributes_internal-use.md.J0q2AD4y.lean.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as n,R as e}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"[InternalUse]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/internal-use.md","filePath":"modeling/model-components/attributes/internal-use.md"}'),l={name:"modeling/model-components/attributes/internal-use.md"},o=e("",9),p=[o];function t(r,c,D,i,y,C){return a(),n("div",null,p)}const u=s(l,[["render",t]]);export{m as __pageData,u as default}; diff --git a/assets/modeling_model-components_attributes_list-text.md.6SOGN1hl.js b/assets/modeling_model-components_attributes_list-text.md.6SOGN1hl.js new file mode 100644 index 000000000..fae634537 --- /dev/null +++ b/assets/modeling_model-components_attributes_list-text.md.6SOGN1hl.js @@ -0,0 +1,12 @@ +import{_ as s,o as a,c as n,R as e}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"[ListText]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/list-text.md","filePath":"modeling/model-components/attributes/list-text.md"}'),l={name:"modeling/model-components/attributes/list-text.md"},t=e(`

[ListText]

When a textual representation of an object needs to be displayed in the UI, this attribute controls which property will be used. Examples include dropdowns and cells in admin UI tables.

If this attribute is not used, and a property named Name exists on the model, that property will be used. Otherwise, the primary key will be used.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+
+    public string FirstName { get; set; }
+
+    public string LastName { get; set; }
+
+    [ListText]
+    [NotMapped]
+    public string Name => FirstName + " " + LastName
+}
`,5),p=[t];function o(c,r,i,D,y,d){return a(),n("div",null,p)}const u=s(l,[["render",o]]);export{m as __pageData,u as default}; diff --git a/assets/modeling_model-components_attributes_list-text.md.6SOGN1hl.lean.js b/assets/modeling_model-components_attributes_list-text.md.6SOGN1hl.lean.js new file mode 100644 index 000000000..6797c0afe --- /dev/null +++ b/assets/modeling_model-components_attributes_list-text.md.6SOGN1hl.lean.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as n,R as e}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"[ListText]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/list-text.md","filePath":"modeling/model-components/attributes/list-text.md"}'),l={name:"modeling/model-components/attributes/list-text.md"},t=e("",5),p=[t];function o(c,r,i,D,y,d){return a(),n("div",null,p)}const u=s(l,[["render",o]]);export{m as __pageData,u as default}; diff --git a/assets/modeling_model-components_attributes_load-from-data-source.md.mGs5QFTJ.js b/assets/modeling_model-components_attributes_load-from-data-source.md.mGs5QFTJ.js new file mode 100644 index 000000000..4f9b0d56b --- /dev/null +++ b/assets/modeling_model-components_attributes_load-from-data-source.md.mGs5QFTJ.js @@ -0,0 +1,11 @@ +import{_ as o,D as n,o as l,c as t,I as p,R as r,k as s,a}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse('{"title":"[LoadFromDataSource]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/load-from-data-source.md","filePath":"modeling/model-components/attributes/load-from-data-source.md"}'),c={name:"modeling/model-components/attributes/load-from-data-source.md"},D=r(`

[LoadFromDataSource]

Specifies that the targeted model instance method should load the instance it is called on from the specified data source when invoked from an API endpoint. If not defined, the model's default data source is used.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    public string FirstName { get; set; }
+
+    [Coalesce, LoadFromDataSource(typeof(WithoutCases))]
+    public void ChangeSpacesToDashesInName()
+    {
+        FirstName = FirstName.Replace(" ", "-");
+    }
+}

Properties

`,5),i=s("p",null,[a("The name of the "),s("a",{href:"/Coalesce/modeling/model-components/data-sources.html"},"Data Source"),a(" to load the instance object from.")],-1);function d(m,y,u,C,h,_){const e=n("Prop");return l(),t("div",null,[D,p(e,{def:"public Type DataSourceType { get; }",ctor:"1"}),i])}const b=o(c,[["render",d]]);export{g as __pageData,b as default}; diff --git a/assets/modeling_model-components_attributes_load-from-data-source.md.mGs5QFTJ.lean.js b/assets/modeling_model-components_attributes_load-from-data-source.md.mGs5QFTJ.lean.js new file mode 100644 index 000000000..ed884fa44 --- /dev/null +++ b/assets/modeling_model-components_attributes_load-from-data-source.md.mGs5QFTJ.lean.js @@ -0,0 +1 @@ +import{_ as o,D as n,o as l,c as t,I as p,R as r,k as s,a}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse('{"title":"[LoadFromDataSource]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/load-from-data-source.md","filePath":"modeling/model-components/attributes/load-from-data-source.md"}'),c={name:"modeling/model-components/attributes/load-from-data-source.md"},D=r("",5),i=s("p",null,[a("The name of the "),s("a",{href:"/Coalesce/modeling/model-components/data-sources.html"},"Data Source"),a(" to load the instance object from.")],-1);function d(m,y,u,C,h,_){const e=n("Prop");return l(),t("div",null,[D,p(e,{def:"public Type DataSourceType { get; }",ctor:"1"}),i])}const b=o(c,[["render",d]]);export{g as __pageData,b as default}; diff --git a/assets/modeling_model-components_attributes_many-to-many.md.66__Haw9.js b/assets/modeling_model-components_attributes_many-to-many.md.66__Haw9.js new file mode 100644 index 000000000..d554c82e9 --- /dev/null +++ b/assets/modeling_model-components_attributes_many-to-many.md.66__Haw9.js @@ -0,0 +1,9 @@ +import{_ as n,D as o,o as t,c as l,I as a,R as p,k as e}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse('{"title":"[ManyToMany]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/many-to-many.md","filePath":"modeling/model-components/attributes/many-to-many.md"}'),r={name:"modeling/model-components/attributes/many-to-many.md"},c=p(`

[ManyToMany]

Used to specify a Many to Many relationship. Because EF core does not support automatic intermediate mapping tables, this field is used to allow for direct reference of the many-to-many collections from the ViewModel.

The named specified in the attribute will be used as the name of a collection of the objects on the other side of the relationship in the generated TypeScript ViewModels.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    public string FirstName { get; set; }
+    public string LastName { get; set; }
+
+    [ManyToMany("Appointments")]
+    public ICollection<PersonAppointment> PersonAppointments { get; set; }
+}

Properties

`,6),i=e("p",null,"The name of the collection that will contain the set of objects on the other side of the many-to-many relationship.",-1),y=e("p",null,"The name of the navigation property on the middle entity that points at the far side of the many-to-many relationship. Use this to resolve ambiguities when the middle table of the many-to-many relationship has more than two reference navigation properties on it.",-1);function D(m,d,h,u,C,f){const s=o("Prop");return t(),l("div",null,[c,a(s,{def:"public string CollectionName { get; }",ctor:"1"}),i,a(s,{def:"public string FarNavigationProperty { get; set; }"}),y])}const b=n(r,[["render",D]]);export{g as __pageData,b as default}; diff --git a/assets/modeling_model-components_attributes_many-to-many.md.66__Haw9.lean.js b/assets/modeling_model-components_attributes_many-to-many.md.66__Haw9.lean.js new file mode 100644 index 000000000..53ec7a421 --- /dev/null +++ b/assets/modeling_model-components_attributes_many-to-many.md.66__Haw9.lean.js @@ -0,0 +1 @@ +import{_ as n,D as o,o as t,c as l,I as a,R as p,k as e}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse('{"title":"[ManyToMany]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/many-to-many.md","filePath":"modeling/model-components/attributes/many-to-many.md"}'),r={name:"modeling/model-components/attributes/many-to-many.md"},c=p("",6),i=e("p",null,"The name of the collection that will contain the set of objects on the other side of the many-to-many relationship.",-1),y=e("p",null,"The name of the navigation property on the middle entity that points at the far side of the many-to-many relationship. Use this to resolve ambiguities when the middle table of the many-to-many relationship has more than two reference navigation properties on it.",-1);function D(m,d,h,u,C,f){const s=o("Prop");return t(),l("div",null,[c,a(s,{def:"public string CollectionName { get; }",ctor:"1"}),i,a(s,{def:"public string FarNavigationProperty { get; set; }"}),y])}const b=n(r,[["render",D]]);export{g as __pageData,b as default}; diff --git a/assets/modeling_model-components_attributes_restrict.md.LYUDlSbo.js b/assets/modeling_model-components_attributes_restrict.md.LYUDlSbo.js new file mode 100644 index 000000000..0f52bb8f0 --- /dev/null +++ b/assets/modeling_model-components_attributes_restrict.md.LYUDlSbo.js @@ -0,0 +1,23 @@ +import{_ as s,o as n,c as a,R as e}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"[Restrict]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/restrict.md","filePath":"modeling/model-components/attributes/restrict.md"}'),o={name:"modeling/model-components/attributes/restrict.md"},l=e(`

[Restrict]

In addition to role-based property restrictions, you can also define property restrictions that can execute custom code for each model instance if your logic require more nuanced decisions than can be made with roles.

c#
using IntelliTect.Coalesce.DataAnnotations;
+public class Employee 
+{
+  public int Id { get; set; }
+
+  [Read]
+  public string UserId { get; set; }
+
+  [Restrict<SalaryRestriction>]
+  public decimal Salary { get; set; }
+}
+
+public class SalaryRestriction(MyUserService userService) : IPropertyRestriction<Employee>
+{
+  public bool UserCanRead(IMappingContext context, string propertyName, Employee model)
+    => context.User.GetUserId() == model.UserId || userService.IsPayroll(context.User);
+
+  public bool UserCanWrite(IMappingContext context, string propertyName, Employee model, object incomingValue)
+    => userService.IsPayroll(context.User);
+
+  public bool UserCanFilter(IMappingContext context, string propertyName)
+    => userService.IsPayroll(context.User);
+}

Restriction classes support dependency injection, so you can inject any supplemental services needed to make a determination.

The UserCanRead method controls whether values of the restricted property will be mapped from model instances to the generated DTO. Similarly, UserCanWrite controls whether the property can be mapped back to the model instance from the generated DTO.

The UserCanFilter method has a default implementation that returns false, but can be implemented if there is an appropriate, instance-agnostic way to determine if a user can sort, search, or filter values of that property.

Multiple different restrictions can be placed on a single property; all of them must succeed for the operation to be permitted. Restrictions also stack on top of role attribute restrictions ([Read] and [Edit]).

A non-generic variant of IPropertyRestriction also exists for restrictions that might be reused across multiple model types.

`,8),p=[l];function t(r,c,D,y,i,C){return n(),a("div",null,p)}const u=s(o,[["render",t]]);export{m as __pageData,u as default}; diff --git a/assets/modeling_model-components_attributes_restrict.md.LYUDlSbo.lean.js b/assets/modeling_model-components_attributes_restrict.md.LYUDlSbo.lean.js new file mode 100644 index 000000000..1110ae7ce --- /dev/null +++ b/assets/modeling_model-components_attributes_restrict.md.LYUDlSbo.lean.js @@ -0,0 +1 @@ +import{_ as s,o as n,c as a,R as e}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"[Restrict]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/restrict.md","filePath":"modeling/model-components/attributes/restrict.md"}'),o={name:"modeling/model-components/attributes/restrict.md"},l=e("",8),p=[l];function t(r,c,D,y,i,C){return n(),a("div",null,p)}const u=s(o,[["render",t]]);export{m as __pageData,u as default}; diff --git a/assets/modeling_model-components_attributes_search.md.q7FurM4k.js b/assets/modeling_model-components_attributes_search.md.q7FurM4k.js new file mode 100644 index 000000000..2202bd714 --- /dev/null +++ b/assets/modeling_model-components_attributes_search.md.q7FurM4k.js @@ -0,0 +1,18 @@ +import{_ as n,D as l,o as r,c as i,I as s,R as o,k as e,a as t}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"[Search]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/search.md","filePath":"modeling/model-components/attributes/search.md"}'),c={name:"modeling/model-components/attributes/search.md"},p=o(`

[Search]

Coalesce supports searching through the generated API in its various implementations, including the generated list views (Table & Cards), in Select2 dropdowns, and directly through the TypeScript ListViewModels' search property.

The search parameter of the API can also be formatted as PropertyName:SearchTerm in order to search on an arbitrary property of a model. For example, a value of Nickname:Steve-o for a search term would search the Nickname property, even through it is not marked as searchable using this attribute.

By default, the system will search any field with the name 'Name'. If this doesn't exist, the ID is used as the only searchable field. Once you place the Search attribute on one or more properties on a model, only those annotated properties will be searched.

Searchable Property Types

Strings

String fields will be searched based on the SearchMethod property on the attribute. See below.

Numeric Types

If the input is numeric, numeric fields will be searched for the exact value.

Enums

If the input is a valid name of an enum value for an enum property and that property is searchable, rows will be searched for the exact value.

Dates

If the input is a parsable date, rows will be searched based on that date.

Date search will do its best to guess at the user's intentions:

  • Various forms of year/month combos are supported, and if only a year/month is inputted, it will look for all dates in that month, e.g. "Feb 2017" or "2016-11".
  • A date without a time (or a time of exactly midnight) will search the entire day, e.g. "2017/4/18".
  • A date/time with minutes and seconds equal to 0 will search the entire hour, e.g. "April 7, 2017 11 AM".

TIP

When searching on date properties, you should almost always set IsSplitOnSpaces = false on the Search attribute. This allows natural inputs like "July 21, 2017" to search correctly. Otherwise, only non-whitespace date formats will work, like "2017/21/07".

Reference Navigation Properties

When a reference navigation property is marked with [Search], searchable properties on the referenced object will also be searched. This behavior will go up to two levels away from the root object, and can be controlled with the RootWhitelist and RootBlacklist properties on the [Search] attribute that are outlined below.

Collection Navigation Properties

When a collection navigation property is marked with [Search], searchable properties on the child objects will also be searched. This behavior will go up to two levels away from the root object, and can be controlled with the RootWhitelist and RootBlacklist properties on the [Search] attribute that are outlined below.

WARNING

Searches on collection navigation properties usually don't translate well with EF Core, leading to potentially degraded performance. Use this feature cautiously.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+
+    [Search]
+    public string FirstName { get; set; }
+
+    [Search]
+    public string LastName { get; set; }
+
+    [Search(IsSplitOnSpaces = false)]
+    public string BirthDate { get; set; }
+
+    public string Nickname { get; set; }
+
+    [Search(RootWhitelist = nameof(Person))]
+    public ICollection<Address> Addresses { get; set; }
+}

Properties

`,24),h=e("p",null,[t("If set to true (the default), each word in the search terms will be searched for in each searchable field independently, and a row will only be considered a match if each word in the search term is a match on at least one searchable property where "),e("code",null,"IsSplitOnSpaces == true")],-1),d=e("p",null,[t("This is useful when searching for a full name across two or more fields. In the above example, using "),e("code",null,"IsSplitOnSpaces = true"),t(" would provide more intuitive behavior since it will search both first name and last name for each word entered into the search field. But, "),e("a",{href:"https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/",target:"_blank",rel:"noreferrer"},"you probably shouldn't be doing that"),t(".")],-1),u=o("

For string properties, specifies how the value in the property/column will be matched.

  • BeginsWith: Search term will be checked for at the beginning of the field's value in a case insensitive manner.
  • Equals: Search term must match the field exactly in a case insensitive manner.
  • EqualsNatural: Search term must match exactly, using the natural casing handling of the evaluation environment. Default database collation will be used if evaluated in SQL, and exact casing will be used if evaluated in memory. This allows index seeks to be used instead of index scans, providing extra high performance searches against indexed columns
  • Contains: Search term will be checked for anywhere inside the field's value in a case insensitive manner. Will be slow against large databases - performance cannot be improved with database indexing.
",2),D=e("p",null,"A comma-delimited list of model class names that, if set, will prevent the targeted property from being searched unless the root object of the API call was one of the specified class names.",-1),m=e("p",null,"A comma-delimited list of model class names that, if set, will prevent the targeted property from being searched if the root object of the API call was one of the specified class names.",-1);function y(b,f,g,w,C,v){const a=l("Prop");return r(),i("div",null,[p,s(a,{def:"public bool IsSplitOnSpaces { get; set; } = true;"}),h,d,s(a,{def:"public SearchMethods SearchMethod { get; set; } = SearchMethods.BeginsWith;"}),u,s(a,{def:"public string RootWhitelist { get; set; } = null;"}),D,s(a,{def:"public string RootBlacklist { get; set; } = null;"}),m])}const k=n(c,[["render",y]]);export{_ as __pageData,k as default}; diff --git a/assets/modeling_model-components_attributes_search.md.q7FurM4k.lean.js b/assets/modeling_model-components_attributes_search.md.q7FurM4k.lean.js new file mode 100644 index 000000000..09206395d --- /dev/null +++ b/assets/modeling_model-components_attributes_search.md.q7FurM4k.lean.js @@ -0,0 +1 @@ +import{_ as n,D as l,o as r,c as i,I as s,R as o,k as e,a as t}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"[Search]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/search.md","filePath":"modeling/model-components/attributes/search.md"}'),c={name:"modeling/model-components/attributes/search.md"},p=o("",24),h=e("p",null,[t("If set to true (the default), each word in the search terms will be searched for in each searchable field independently, and a row will only be considered a match if each word in the search term is a match on at least one searchable property where "),e("code",null,"IsSplitOnSpaces == true")],-1),d=e("p",null,[t("This is useful when searching for a full name across two or more fields. In the above example, using "),e("code",null,"IsSplitOnSpaces = true"),t(" would provide more intuitive behavior since it will search both first name and last name for each word entered into the search field. But, "),e("a",{href:"https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/",target:"_blank",rel:"noreferrer"},"you probably shouldn't be doing that"),t(".")],-1),u=o("",2),D=e("p",null,"A comma-delimited list of model class names that, if set, will prevent the targeted property from being searched unless the root object of the API call was one of the specified class names.",-1),m=e("p",null,"A comma-delimited list of model class names that, if set, will prevent the targeted property from being searched if the root object of the API call was one of the specified class names.",-1);function y(b,f,g,w,C,v){const a=l("Prop");return r(),i("div",null,[p,s(a,{def:"public bool IsSplitOnSpaces { get; set; } = true;"}),h,d,s(a,{def:"public SearchMethods SearchMethod { get; set; } = SearchMethods.BeginsWith;"}),u,s(a,{def:"public string RootWhitelist { get; set; } = null;"}),D,s(a,{def:"public string RootBlacklist { get; set; } = null;"}),m])}const k=n(c,[["render",y]]);export{_ as __pageData,k as default}; diff --git a/assets/modeling_model-components_attributes_security-attribute.md.jOlvg0wG.js b/assets/modeling_model-components_attributes_security-attribute.md.jOlvg0wG.js new file mode 100644 index 000000000..2bf34c930 --- /dev/null +++ b/assets/modeling_model-components_attributes_security-attribute.md.jOlvg0wG.js @@ -0,0 +1,27 @@ +import{_ as l,D as n,o as r,c as p,I as o,a as e,R as t,k as s}from"./chunks/framework.g9eZ-ZSs.js";const v=JSON.parse('{"title":"Security Attributes","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/security-attribute.md","filePath":"modeling/model-components/attributes/security-attribute.md"}'),c={name:"modeling/model-components/attributes/security-attribute.md"},i=t(`

Security Attributes

Coalesce provides a collection of attributes which can provide class-level (and property-level, where appropriate) security controls over the generated API.

TIP

This page provides API-level documentation for a specific set of attributes. For a complete overview of all the security-focused techniques that can be used in a Coalesce application, see the Security page.

Class vs. Property Security

There are important differences between class-level security and property-level security, beyond the usage of the attributes themselves. In general, class-level security is implemented in the generated API Controllers as [Authorize] attributes on the generated actions. Property security attributes are implemented in the Generated C# DTOs.

Implementations

[Read]

Controls permissions for reading of objects and properties through the API.

For property-level security only, if a [Read] attribute is present without an [Edit] attribute, the property is read-only.

Additionally, you can set NoAutoInclude = true the [Read] attribute to suppress the Default Loading Behavior.

Example Usage

c#
[Read(Roles = "Management", PermissionLevel = SecurityPermissionLevels.AllowAuthorized)]
+public class Employee
+{
+    public int EmployeeId { get; set; }
+
+    [Read("Payroll")]
+    public string LastFourSsn { get; set; }
+    
+    ...
+}

[Edit]

Controls permissions for editing of objects and properties through the API.

For property-level security only, if a [Read] attribute is present, one of its roles must be fulfilled in addition to the roles specified (if any) for the [Edit] attribute.

Example Usage

c#
[Edit(Roles = "Management,Payroll", PermissionLevel = SecurityPermissionLevels.AllowAuthorized)]
+public class Employee
+{
+    public int EmployeeId { get; set; }
+
+    [Read("Payroll,HumanResources"), Edit("Payroll")]
+    public string LastFourSsn { get; set; }
+    
+    ...
+}

[Create]

Controls permissions for creation of an object of the targeted type through the API.

Example Usage

c#
[Create(Roles = "HumanResources", PermissionLevel = SecurityPermissionLevels.AllowAuthorized)]
+public class Employee
+{
+    ...
+}

[Delete]

Controls permissions for deletion of an object of the targeted type through the API.

Example Usage

c#
[Delete(Roles = "HumanResources,Management", PermissionLevel = SecurityPermissionLevels.AllowAuthorized)]
+public class Employee
+{
+    ...
+}

[Execute]

A separate attribute for controlling method execution exists. Its documentation may be found on the [Execute] page.

[Restrict]

For property security, [Read] and [Edit] can be used to apply role-based security. If you need logic more complicated than checking for the presence of a role, [Restrict] offers the ability to write custom code to control the read and/or write permissions of a property.

Attribute Properties

`,30),D=s("p",null,"A comma-delimited list of roles that are authorized to take perform the action represented by the attribute. If the current user belongs to any of the listed roles, the action will be allowed.",-1),d=s("p",null,[e("The string set for this property will be outputted as an "),s("code",null,'[Authorize(Roles="RolesString")]'),e(" attribute on generated API controller actions.")],-1),u=t("

The level of access to allow for the action for class-level security only. Has no effect for property-level security.

Enum values are:

  • SecurityPermissionLevels.AllowAll Allow all users to perform the action for the attribute, including users who are not authenticated at all.
  • SecurityPermissionLevels.AllowAuthorized Allow only users who are members of the roles specified on the attribute to perform the action. If no roles are specified on the attribute, then all authenticated users are allowed (no anonymous access).
  • SecurityPermissionLevels.DenyAll Deny the action to all users, regardless of authentication status or authorization level. If DenyAll is used, no API endpoint for the action will be generated.
",3);function y(h,m,C,b,f,g){const a=n("Prop");return r(),p("div",null,[i,o(a,{def:"public string Roles { get; set; }",ctor:"1"}),e(),D,d,o(a,{def:"public SecurityPermissionLevels PermissionLevel { get; set; }",ctor:"2"}),e(),u])}const P=l(c,[["render",y]]);export{v as __pageData,P as default}; diff --git a/assets/modeling_model-components_attributes_security-attribute.md.jOlvg0wG.lean.js b/assets/modeling_model-components_attributes_security-attribute.md.jOlvg0wG.lean.js new file mode 100644 index 000000000..831a4e22d --- /dev/null +++ b/assets/modeling_model-components_attributes_security-attribute.md.jOlvg0wG.lean.js @@ -0,0 +1 @@ +import{_ as l,D as n,o as r,c as p,I as o,a as e,R as t,k as s}from"./chunks/framework.g9eZ-ZSs.js";const v=JSON.parse('{"title":"Security Attributes","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/security-attribute.md","filePath":"modeling/model-components/attributes/security-attribute.md"}'),c={name:"modeling/model-components/attributes/security-attribute.md"},i=t("",30),D=s("p",null,"A comma-delimited list of roles that are authorized to take perform the action represented by the attribute. If the current user belongs to any of the listed roles, the action will be allowed.",-1),d=s("p",null,[e("The string set for this property will be outputted as an "),s("code",null,'[Authorize(Roles="RolesString")]'),e(" attribute on generated API controller actions.")],-1),u=t("",3);function y(h,m,C,b,f,g){const a=n("Prop");return r(),p("div",null,[i,o(a,{def:"public string Roles { get; set; }",ctor:"1"}),e(),D,d,o(a,{def:"public SecurityPermissionLevels PermissionLevel { get; set; }",ctor:"2"}),e(),u])}const P=l(c,[["render",y]]);export{v as __pageData,P as default}; diff --git a/assets/modeling_model-components_attributes_select-filter.md.O79G0Zlu.js b/assets/modeling_model-components_attributes_select-filter.md.O79G0Zlu.js new file mode 100644 index 000000000..73ec24622 --- /dev/null +++ b/assets/modeling_model-components_attributes_select-filter.md.O79G0Zlu.js @@ -0,0 +1,19 @@ +import{_ as o,D as n,o as p,c as t,I as l,R as c,k as s,a as e}from"./chunks/framework.g9eZ-ZSs.js";const v=JSON.parse('{"title":"[SelectFilter]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/select-filter.md","filePath":"modeling/model-components/attributes/select-filter.md"}'),r={name:"modeling/model-components/attributes/select-filter.md"},D=c(`

[SelectFilter]

WARNING

This attribute only affects the generated Knockout HTML views - it does not enforce any relational rules in your data.

This attribute also currently has no effect against the Vue stack.

Specify a property to restrict dropdown menus by. Values presented will be only those where the value of the foreign property matches the value of the local property.

The local property name defaults to the same value of the foreign property.

Additionally, in place of a LocalPropertyName to check against, you may instead specify a static value using StaticPropertyValue to filter by a constant.

Example Usage

In this example, a dropdown for EmployeeRank created using @Knockout.SelectForObject in cshtml files will only present possible values of EmployeeRank which are valid for the EmployeeType of the Employee.

c#
public class Employee
+{
+    public int EmployeeId { get; set; }
+    public int EmployeeTypeId { get; set; }
+    public EmployeeType EmployeeType { get; set; }
+    public int EmployeeRankId { get; set; }
+
+    [SelectFilter(ForeignPropertyName = nameof(EmployeeRank.EmployeeTypeId), LocalPropertyName = nameof(Employee.EmployeeTypeId))]
+    public EmployeeRank EmployeeRank { get; set; }
+}
+
+public class EmployeeRank
+{
+    public int EmployeeRankId { get; set; }
+    public int EmployeeTypeId { get; set; }
+    public EmployeeType EmployeeType { get; set; }
+}
razor
<div>
+    @(Knockout.SelectForObject<Models.Employee>(e => e.EmployeeRank))
+</div>

Properties

`,10),y=s("p",null,"The name of the property on the foreign object to filter against.",-1),i=s("p",null,"The name of another property belonging to the class in which this attribute is used. The results of select lists will be filtered to match this value.",-1),d=s("p",null,[e("Defaults to the value of "),s("code",null,"ForeignPropertyName"),e(" if not set.")],-1),C=s("p",null,[e("If specified, the "),s("code",null,"LocalPropertyName"),e(" will be resolved from the property by this name that resides on the local object.")],-1),u=s("p",null,"This allows for querying against properties that are one level away from the current object.",-1),h=s("p",null,[e("A constant value that the foreign property will be filtered against. This string must be parsable into the foreign property's type to have any effect. If this is set, "),s("code",null,"LocalPropertyName"),e(" will be ignored.")],-1);function m(f,g,E,b,_,F){const a=n("Prop");return p(),t("div",null,[D,l(a,{def:"public string ForeignPropertyName { get; set; }"}),y,l(a,{def:"public string LocalPropertyName { get; set; }"}),i,d,l(a,{def:"public string LocalPropertyObjectName { get; set; }"}),C,u,l(a,{def:"public string StaticPropertyValue { get; set; }"}),h])}const T=o(r,[["render",m]]);export{v as __pageData,T as default}; diff --git a/assets/modeling_model-components_attributes_select-filter.md.O79G0Zlu.lean.js b/assets/modeling_model-components_attributes_select-filter.md.O79G0Zlu.lean.js new file mode 100644 index 000000000..ccd4ef210 --- /dev/null +++ b/assets/modeling_model-components_attributes_select-filter.md.O79G0Zlu.lean.js @@ -0,0 +1 @@ +import{_ as o,D as n,o as p,c as t,I as l,R as c,k as s,a as e}from"./chunks/framework.g9eZ-ZSs.js";const v=JSON.parse('{"title":"[SelectFilter]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/select-filter.md","filePath":"modeling/model-components/attributes/select-filter.md"}'),r={name:"modeling/model-components/attributes/select-filter.md"},D=c("",10),y=s("p",null,"The name of the property on the foreign object to filter against.",-1),i=s("p",null,"The name of another property belonging to the class in which this attribute is used. The results of select lists will be filtered to match this value.",-1),d=s("p",null,[e("Defaults to the value of "),s("code",null,"ForeignPropertyName"),e(" if not set.")],-1),C=s("p",null,[e("If specified, the "),s("code",null,"LocalPropertyName"),e(" will be resolved from the property by this name that resides on the local object.")],-1),u=s("p",null,"This allows for querying against properties that are one level away from the current object.",-1),h=s("p",null,[e("A constant value that the foreign property will be filtered against. This string must be parsable into the foreign property's type to have any effect. If this is set, "),s("code",null,"LocalPropertyName"),e(" will be ignored.")],-1);function m(f,g,E,b,_,F){const a=n("Prop");return p(),t("div",null,[D,l(a,{def:"public string ForeignPropertyName { get; set; }"}),y,l(a,{def:"public string LocalPropertyName { get; set; }"}),i,d,l(a,{def:"public string LocalPropertyObjectName { get; set; }"}),C,u,l(a,{def:"public string StaticPropertyValue { get; set; }"}),h])}const T=o(r,[["render",m]]);export{v as __pageData,T as default}; diff --git a/assets/modeling_model-components_attributes_typescript-partial.md.4Crd20VW.js b/assets/modeling_model-components_attributes_typescript-partial.md.4Crd20VW.js new file mode 100644 index 000000000..e5ac9a3ae --- /dev/null +++ b/assets/modeling_model-components_attributes_typescript-partial.md.4Crd20VW.js @@ -0,0 +1,7 @@ +import{_ as a,D as s,o as t,c as o,I as n,R as l,k as p}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"[TypeScriptPartial]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/typescript-partial.md","filePath":"modeling/model-components/attributes/typescript-partial.md"}'),r={name:"modeling/model-components/attributes/typescript-partial.md"},i=l(`

[TypeScriptPartial]

Note

This attribute only applies to the Knockout front-end stack. It is not applicable to the Vue stack.

If defined on a model, a typescript file will be generated in ./Scripts/Partials if one does not already exist. This 'Partial' TypeScript file contains a class which inherits from the generated TypeScript ViewModel. The partial class has the same name as the generated ViewModel would normally have, and the generated ViewModel is renamed to "<ClassName>Partial".

This behavior allows you to extend the behavior of the generated TypeScript view models with your own properties and methods for defining more advanced behavior on the client. One of the most common use cases of this is to define additional Knockout ComputedObservable properties for information that is only useful in the browser - for example, computing a css class based on data in the object.

Example Usage

c#
[TypeScriptPartial]
+public class Employee
+{
+    public int EmployeeId { get; set; }
+
+    ...
+}

Properties

`,7),c=p("p",null,"If set, overrides the name of the generated ViewModel which becomes the base class for the generated 'Partial' TypeScript file.",-1);function d(h,m,y,u,D,f){const e=s("Prop");return t(),o("div",null,[i,n(e,{def:"public string BaseClassName { get; set; }"}),c])}const g=a(r,[["render",d]]);export{_ as __pageData,g as default}; diff --git a/assets/modeling_model-components_attributes_typescript-partial.md.4Crd20VW.lean.js b/assets/modeling_model-components_attributes_typescript-partial.md.4Crd20VW.lean.js new file mode 100644 index 000000000..767e004e4 --- /dev/null +++ b/assets/modeling_model-components_attributes_typescript-partial.md.4Crd20VW.lean.js @@ -0,0 +1 @@ +import{_ as a,D as s,o as t,c as o,I as n,R as l,k as p}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"[TypeScriptPartial]","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/attributes/typescript-partial.md","filePath":"modeling/model-components/attributes/typescript-partial.md"}'),r={name:"modeling/model-components/attributes/typescript-partial.md"},i=l("",7),c=p("p",null,"If set, overrides the name of the generated ViewModel which becomes the base class for the generated 'Partial' TypeScript file.",-1);function d(h,m,y,u,D,f){const e=s("Prop");return t(),o("div",null,[i,n(e,{def:"public string BaseClassName { get; set; }"}),c])}const g=a(r,[["render",d]]);export{_ as __pageData,g as default}; diff --git a/assets/modeling_model-components_behaviors.md.34ydZMzc.js b/assets/modeling_model-components_behaviors.md.34ydZMzc.js new file mode 100644 index 000000000..1b23f219b --- /dev/null +++ b/assets/modeling_model-components_behaviors.md.34ydZMzc.js @@ -0,0 +1,70 @@ +import{_ as o,D as l,o as r,c as p,I as t,R as n,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const K=JSON.parse('{"title":"Behaviors","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/behaviors.md","filePath":"modeling/model-components/behaviors.md"}'),c={name:"modeling/model-components/behaviors.md"},i=n(`

Behaviors

In a CRUD system, creating, updating, and deleting are considered especially different from reading. In Coalesce, the dedicated classes that perform these operations are derivatives of a special interface known as the IBehaviors<T>. These are their stories.


Coalesce separates out the parts of your API that read your data from the parts that mutate it. The read portion is performed by Data Sources, and the mutations are performed by behaviors. Like data sources, there exists a standard set of behaviors that Coalesce provides out-of-the-box that cover the most common use cases for creating, updating, and deleting objects in your data model.

Also like data sources, these functions can be easily overridden on a per-model basis, allowing complete control over the ways in which your data is mutated by the APIs that Coalesce generates. However, unlike data sources which can have as many implementations per model as you like, you can only have one set of behaviors.

Defining Behaviors

By default, each of your models that Coalesce exposes will utilize the standard behaviors (IntelliTect.Coalesce.StandardBehaviors<T, TContext>) for the out-of-the-box API endpoints that Coalesce provides. These behaviors provide a set of create, update, and delete methods for an EF Core DbContext, as well as a plethora of virtual methods that make the StandardBehaviors a great base class for your custom implementations. Unlike data sources which require an annotation to override the Coalesce-provided standard class, the simple presence of an explicitly declared set of behaviors will suppress the standard behaviors.

Note

When you define a set of custom behaviors, take note that these are only used by the standard set of API endpoints that Coalesce always provides. They will not be used to handle any mutations in any Methods you write for your models.

To create your own behaviors, you simply need to define a class that implements IntelliTect.Coalesce.IBehaviors<T>. To expose your behaviors to Coalesce, either place it as a nested class of the type T that your behaviors are for, or annotate it with the [Coalesce] attribute. Of course, the easiest way to create behaviors that doesn't require you to re-engineer a great deal of logic would be to inherit from IntelliTect.Coalesce.StandardBehaviors<T, TContext>, and then override only the parts that you need.

c#
public class Case
+{
+    public int CaseId { get; set; }
+    public int OwnerId { get; set; }
+    public bool IsDeleted { get; set; }
+    ...
+}
+
+[Coalesce]
+public class CaseBehaviors : StandardBehaviors<Case, AppDbContext>
+{
+    public CaseBehaviors(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override ItemResult BeforeSave(SaveKind kind, Case oldItem, Case item)
+    {
+        // Allow admins to bypass all validation.
+        if (User.IsInRole("Admin")) return true;
+
+        if (kind == SaveKind.Update && oldItem.OwnerId != item.OwnerId)
+            return "The owner of a case may not be changed";
+
+        // This is a new item, OR its an existing item and the owner isn't being modified.
+        if (item.CreatedById != User.GetUserId())
+            return "You are not the owner of this item.";
+
+        return true;
+    }
+
+    public override ItemResult BeforeDelete(Case item) 
+        => User.IsInRole("Manager") ? true : "Unauthorized";
+
+    public override Task ExecuteDeleteAsync(Case item)
+    {
+        // Soft delete the item.
+        item.IsDeleted = true;
+        return Db.SaveChangesAsync();
+    }
+}

Dependency Injection

All behaviors are instantiated using dependency injection and your application's IServiceProvider. As a result, you can add whatever constructor parameters you desire to your behaviors as long as a value for them can be resolved from your application's services. The single parameter to the StandardBehaviors is resolved in this way - the CrudContext<TContext> contains the common set of objects most commonly used, including the DbContext and the ClaimsPrincipal representing the current user.

Standard Behaviors

The standard behaviors, IntelliTect.Coalesce.StandardBehaviors<T> and its EntityFramework-supporting sibling IntelliTect.Coalesce.StandardBehaviors<T, TContext>, contain a significant number of properties and methods that can be utilized and/or overridden at your leisure.

Properties

`,15),d=e("p",null,"The object passed to the constructor that contains the set of objects needed by the standard behaviors, and those that are most likely to be used in custom implementations.",-1),D=e("p",null,[s("An instance of the db context that contains a "),e("code",null,"DbSet"),s(" for the entity handled by the behaviors")],-1),h=e("p",null,"The user making the current request.",-1),y=e("p",null,"A data source that, if set, will override the data source that is used to retrieve the target of an update operation from the database. The incoming values will then be set on this retrieved object. Null by default; override by setting a value in the constructor.",-1),u=e("p",null,"A data source that, if set, will override the data source that is used to retrieve a newly-created or just-updated object from the database after a save. The retrieved object will be returned to the client. Null by default; override by setting a value in the constructor.",-1),m=e("p",null,"A data source that, if set, will override the data source that is used to retrieve the target of an delete operation from the database. The retrieved object will then be deleted. Null by default; override by setting a value in the constructor.",-1),C=n(`

A data source that, if set, will override the data source that is used to retrieve the target of an delete operation from the database after it has been deleted. If an object is able to be retrieved from this data source, it will be sent back to the client. This allows soft-deleted items to be returned to the client when the user is able to see them. Null by default; override by setting a value in the constructor.

Method Overview

The standard behaviors implementation contains many different methods which can be overridden in your derived class to control functionality.

These methods often call one another, so overriding one method may cause some other method to no longer be called. The hierarchy of method calls, ignoring any logic or conditions contained within, is as follows:

SaveAsync
+    DetermineSaveKindAsync
+    GetDbSet
+    ValidateDto
+    MapIncomingDto
+    BeforeSaveAsync
+        BeforeSave
+    ExecuteSaveAsync
+    AfterSave
+
+DeleteAsync
+    BeforeDeleteAsync
+        BeforeDelete
+    ExecuteDeleteAsync
+        GetDbSet
+    AfterDelete

Method Details

All of the methods outlined above can be overridden. A description of each of the methods is as follows:

`,7),v=e("p",null,[s("Save the given item. This is the main entry point for saving, and takes a DTO as a parameter. This method is responsible for performing mapping to your EF models and ultimately saving to your database. If it is required that you access properties from the incoming DTO in this method, a set of extension methods "),e("code",null,"GetValue"),s(" and "),e("code",null,"GetObject"),s(" are available on the DTO for accessing properties that are mapped 1:1 with your EF models.")],-1),b=e("p",null,"Given the incoming DTO on which Save has been called, examine its properties to determine if the operation is meant to be a create or an update operation. Return this distinction along with the key that was used to make the distinction.",-1),f=e("p",null,"This method is called outside of the standard data source by the base API controller to perform role-based security on saves at the controller level.",-1),T=e("p",null,[s("Returns a "),e("code",null,"DbSet"),s(" that items can be added to (creates) or remove from (deletes).")],-1),g=e("p",null,[s("Provides a chance to validate the properties of the DTO object itself, as opposed to doing validation in "),e("code",null,"BeforeSave"),s(" of the properties of the model after the DTO has been mapped to the model. This also where "),e("a",{href:"/Coalesce/topics/security.html#attribute-validation"},"attribute-based validation"),s(" is performed.")],-1),_=e("p",null,[s("To perform custom validation in this method (uncommon), there are a number of extension methods on "),e("code",null,"IClassDto"),s(" that can be used to access the value of the properties of "),e("a",{href:"/Coalesce/stacks/agnostic/dtos.html"},"Generated C# DTOs"),s(". For behaviors on "),e("a",{href:"/Coalesce/modeling/model-types/dtos.html"},"Custom DTOs"),s(" where the DTO type is known, simply cast to the correct type.")],-1),w=n("

Map the properties of the incoming DTO to the model that will be saved to the database. For a SaveKind.Create, this will call the MapToNew method on the DTO and a new instance must be returned (item will be null). For a SaveKind.Update, this will call the MapTo method on the DTO, and the incoming item must be returned. If more precise control is needed, extension methods on IClassDto<T> or casting to a known type can be used to get specific values. If all else fails, the DTO can be reflected upon.

",1),S=e("p",null,"Provides an easy way for derived classes to intercept a save attempt and either reject it by returning an unsuccessful result, or approve it by returning success. The incoming item can also be modified at will in this method to override changes that the client made as desired.",-1),I=e("p",null,[s("Provides an easy way for derived classes to perform actions after a save operation has been completed. Failure results returned here will present an error to the client, but will not prevent modifications to the database since changes have already been saved at this point. This method can optionally modify or replace the item that is sent back to the client after a save by setting "),e("code",null,"ref T item"),s(" to another object or to null. Setting "),e("code",null,"ref IncludeTree includeTree"),s(" will override the "),e("a",{href:"/Coalesce/concepts/include-tree.html"},"Include Tree"),s(" used to shape the response object.")],-1),A=e("div",{class:"warning custom-block"},[e("p",{class:"custom-block-title"},"WARNING"),e("p",null,[s("Setting "),e("code",null,"ref T item"),s(" to null will prevent the new object from being returned - be aware that this can be harmful in create scenarios since it prevents the client from receiving the primary key of the newly created item. If autoSave is enabled on the client, this could cause a large number of duplicate objects to be created in the database, since each subsequent save by the client will be treated as a create when the incoming object lacks a primary key.")])],-1),E=e("p",null,"Deletes the given item.",-1),B=e("p",null,"Provides an easy way to intercept a delete request and potentially reject it (by returning a non-success ItemResult).",-1),k=e("p",null,[s("Performs the delete action against the database. The implementation of this method removes the item from its corresponding "),e("code",null,"DbSet"),s(", and then calls "),e("code",null,"Db.SaveChangesAsync()"),s(".")],-1),x=e("p",null,"Overriding this allows for changing this row-deletion implementation to something else, like setting of a soft delete flag, or copying the data into another archival table before deleting.",-1),F=n(`

Allows for performing any sort of cleanup actions after a delete has completed. If the item was still able to be retrieved from the database after the delete operation completed, this method allows lets you modify or replace the item that is sent back to the client by setting ref T item to another object or to null. Setting ref IncludeTree includeTree will override the Include Tree used to shape the response object.

Globally Replacing the Standard Behaviors

You can, of course, create a custom base behaviors class that all your custom implementations inherit from. But, what if you want to override the standard behaviors across your entire application, so that StandardBehaviors<,> will never be instantiated? You can do that too!

Simply create a class that implements IEntityFrameworkBehaviors<,> (the StandardBehaviors<,> already does - feel free to inherit from it), then register it at application startup like so:

c#
public class MyBehaviors<T, TContext> : StandardBehaviors<T, TContext>
+    where T : class
+    where TContext : DbContext
+{
+    public MyBehaviors(CrudContext<TContext> context) : base(context)
+    {
+    }
+
+    ...
+}
c#
public void ConfigureServices(IServiceCollection services)
+{
+    services.AddCoalesce(b =>
+    {
+        b.AddContext<AppDbContext>();
+        b.UseDefaultBehaviors(typeof(MyBehaviors<,>));
+    });

Your custom behaviors class must have the same generic type parameters - <T, TContext>. Otherwise, the Microsoft.Extensions.DependencyInjection service provider won't know how to inject it.

`,7);function P(q,j,O,R,N,M){const a=l("Prop");return r(),p("div",null,[i,t(a,{def:"CrudContext Context"}),d,t(a,{def:"TContext Db"}),D,t(a,{def:"ClaimsPrincipal User"}),h,t(a,{def:"IDataSource OverrideFetchForUpdateDataSource"}),y,t(a,{def:"IDataSource OverridePostSaveResultDataSource"}),u,t(a,{def:"IDataSource OverrideFetchForDeleteDataSource"}),m,t(a,{def:"IDataSource OverridePostDeleteResultDataSource"}),C,t(a,{def:"Task> SaveAsync(TDto incomingDto, IDataSource dataSource, IDataSourceParameters parameters)"}),v,t(a,{def:"Task<(SaveKind Kind, object? IncomingKey)> DetermineSaveKindAsync(TDto incomingDto, IDataSource dataSource, IDataSourceParameters parameters)"}),b,f,t(a,{def:"DbSet GetDbSet()"}),T,t(a,{def:"ItemResult ValidateDto(SaveKind kind, IClassDto dto)"}),g,_,t(a,{def:"T MapIncomingDto(SaveKind kind, T? item, TDto dto, IDataSourceParameters parameters)"}),w,t(a,{def:`Task BeforeSaveAsync(SaveKind kind, T? oldItem, T item); +ItemResult BeforeSave(SaveKind kind, T? oldItem, T item)`}),S,t(a,{def:"ItemResult AfterSave(SaveKind kind, T? oldItem, ref T item, ref IncludeTree? includeTree)"}),I,A,t(a,{def:"Task> DeleteAsync(object id, IDataSource dataSource, IDataSourceParameters parameters)"}),E,t(a,{def:`Task BeforeDeleteAsync(T item); +ItemResult BeforeDelete(T item)`}),B,t(a,{def:"Task ExecuteDeleteAsync(T item)"}),k,x,t(a,{def:"void AfterDelete(ref T item, ref IncludeTree? includeTree)"}),F])}const U=o(c,[["render",P]]);export{K as __pageData,U as default}; diff --git a/assets/modeling_model-components_behaviors.md.34ydZMzc.lean.js b/assets/modeling_model-components_behaviors.md.34ydZMzc.lean.js new file mode 100644 index 000000000..8be77813d --- /dev/null +++ b/assets/modeling_model-components_behaviors.md.34ydZMzc.lean.js @@ -0,0 +1,3 @@ +import{_ as o,D as l,o as r,c as p,I as t,R as n,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const K=JSON.parse('{"title":"Behaviors","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/behaviors.md","filePath":"modeling/model-components/behaviors.md"}'),c={name:"modeling/model-components/behaviors.md"},i=n("",15),d=e("p",null,"The object passed to the constructor that contains the set of objects needed by the standard behaviors, and those that are most likely to be used in custom implementations.",-1),D=e("p",null,[s("An instance of the db context that contains a "),e("code",null,"DbSet"),s(" for the entity handled by the behaviors")],-1),h=e("p",null,"The user making the current request.",-1),y=e("p",null,"A data source that, if set, will override the data source that is used to retrieve the target of an update operation from the database. The incoming values will then be set on this retrieved object. Null by default; override by setting a value in the constructor.",-1),u=e("p",null,"A data source that, if set, will override the data source that is used to retrieve a newly-created or just-updated object from the database after a save. The retrieved object will be returned to the client. Null by default; override by setting a value in the constructor.",-1),m=e("p",null,"A data source that, if set, will override the data source that is used to retrieve the target of an delete operation from the database. The retrieved object will then be deleted. Null by default; override by setting a value in the constructor.",-1),C=n("",7),v=e("p",null,[s("Save the given item. This is the main entry point for saving, and takes a DTO as a parameter. This method is responsible for performing mapping to your EF models and ultimately saving to your database. If it is required that you access properties from the incoming DTO in this method, a set of extension methods "),e("code",null,"GetValue"),s(" and "),e("code",null,"GetObject"),s(" are available on the DTO for accessing properties that are mapped 1:1 with your EF models.")],-1),b=e("p",null,"Given the incoming DTO on which Save has been called, examine its properties to determine if the operation is meant to be a create or an update operation. Return this distinction along with the key that was used to make the distinction.",-1),f=e("p",null,"This method is called outside of the standard data source by the base API controller to perform role-based security on saves at the controller level.",-1),T=e("p",null,[s("Returns a "),e("code",null,"DbSet"),s(" that items can be added to (creates) or remove from (deletes).")],-1),g=e("p",null,[s("Provides a chance to validate the properties of the DTO object itself, as opposed to doing validation in "),e("code",null,"BeforeSave"),s(" of the properties of the model after the DTO has been mapped to the model. This also where "),e("a",{href:"/Coalesce/topics/security.html#attribute-validation"},"attribute-based validation"),s(" is performed.")],-1),_=e("p",null,[s("To perform custom validation in this method (uncommon), there are a number of extension methods on "),e("code",null,"IClassDto"),s(" that can be used to access the value of the properties of "),e("a",{href:"/Coalesce/stacks/agnostic/dtos.html"},"Generated C# DTOs"),s(". For behaviors on "),e("a",{href:"/Coalesce/modeling/model-types/dtos.html"},"Custom DTOs"),s(" where the DTO type is known, simply cast to the correct type.")],-1),w=n("",1),S=e("p",null,"Provides an easy way for derived classes to intercept a save attempt and either reject it by returning an unsuccessful result, or approve it by returning success. The incoming item can also be modified at will in this method to override changes that the client made as desired.",-1),I=e("p",null,[s("Provides an easy way for derived classes to perform actions after a save operation has been completed. Failure results returned here will present an error to the client, but will not prevent modifications to the database since changes have already been saved at this point. This method can optionally modify or replace the item that is sent back to the client after a save by setting "),e("code",null,"ref T item"),s(" to another object or to null. Setting "),e("code",null,"ref IncludeTree includeTree"),s(" will override the "),e("a",{href:"/Coalesce/concepts/include-tree.html"},"Include Tree"),s(" used to shape the response object.")],-1),A=e("div",{class:"warning custom-block"},[e("p",{class:"custom-block-title"},"WARNING"),e("p",null,[s("Setting "),e("code",null,"ref T item"),s(" to null will prevent the new object from being returned - be aware that this can be harmful in create scenarios since it prevents the client from receiving the primary key of the newly created item. If autoSave is enabled on the client, this could cause a large number of duplicate objects to be created in the database, since each subsequent save by the client will be treated as a create when the incoming object lacks a primary key.")])],-1),E=e("p",null,"Deletes the given item.",-1),B=e("p",null,"Provides an easy way to intercept a delete request and potentially reject it (by returning a non-success ItemResult).",-1),k=e("p",null,[s("Performs the delete action against the database. The implementation of this method removes the item from its corresponding "),e("code",null,"DbSet"),s(", and then calls "),e("code",null,"Db.SaveChangesAsync()"),s(".")],-1),x=e("p",null,"Overriding this allows for changing this row-deletion implementation to something else, like setting of a soft delete flag, or copying the data into another archival table before deleting.",-1),F=n("",7);function P(q,j,O,R,N,M){const a=l("Prop");return r(),p("div",null,[i,t(a,{def:"CrudContext Context"}),d,t(a,{def:"TContext Db"}),D,t(a,{def:"ClaimsPrincipal User"}),h,t(a,{def:"IDataSource OverrideFetchForUpdateDataSource"}),y,t(a,{def:"IDataSource OverridePostSaveResultDataSource"}),u,t(a,{def:"IDataSource OverrideFetchForDeleteDataSource"}),m,t(a,{def:"IDataSource OverridePostDeleteResultDataSource"}),C,t(a,{def:"Task> SaveAsync(TDto incomingDto, IDataSource dataSource, IDataSourceParameters parameters)"}),v,t(a,{def:"Task<(SaveKind Kind, object? IncomingKey)> DetermineSaveKindAsync(TDto incomingDto, IDataSource dataSource, IDataSourceParameters parameters)"}),b,f,t(a,{def:"DbSet GetDbSet()"}),T,t(a,{def:"ItemResult ValidateDto(SaveKind kind, IClassDto dto)"}),g,_,t(a,{def:"T MapIncomingDto(SaveKind kind, T? item, TDto dto, IDataSourceParameters parameters)"}),w,t(a,{def:`Task BeforeSaveAsync(SaveKind kind, T? oldItem, T item); +ItemResult BeforeSave(SaveKind kind, T? oldItem, T item)`}),S,t(a,{def:"ItemResult AfterSave(SaveKind kind, T? oldItem, ref T item, ref IncludeTree? includeTree)"}),I,A,t(a,{def:"Task> DeleteAsync(object id, IDataSource dataSource, IDataSourceParameters parameters)"}),E,t(a,{def:`Task BeforeDeleteAsync(T item); +ItemResult BeforeDelete(T item)`}),B,t(a,{def:"Task ExecuteDeleteAsync(T item)"}),k,x,t(a,{def:"void AfterDelete(ref T item, ref IncludeTree? includeTree)"}),F])}const U=o(c,[["render",P]]);export{K as __pageData,U as default}; diff --git a/assets/modeling_model-components_data-sources.md.bsBPSQ6R.js b/assets/modeling_model-components_data-sources.md.bsBPSQ6R.js new file mode 100644 index 000000000..db22bd0e3 --- /dev/null +++ b/assets/modeling_model-components_data-sources.md.bsBPSQ6R.js @@ -0,0 +1,111 @@ +import{_ as p,D as r,o as c,c as i,I as a,w as l,R as n,k as s,a as e}from"./chunks/framework.g9eZ-ZSs.js";const X=JSON.parse('{"title":"Data Sources","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/data-sources.md","filePath":"modeling/model-components/data-sources.md"}'),D={name:"modeling/model-components/data-sources.md"},d=n(`

Data Sources

In Coalesce, all data that is retrieved from your database through the generated controllers is done so by a data source. These data sources control what data gets loaded and how it gets loaded. By default, there is a single generic data source that serves all data for your models in a generic way that fits many of the most common use cases - the Standard Data Source.

In addition to this standard data source, Coalesce allows you to create custom data sources that provide complete control over the way data is loaded and serialized for transfer to a requesting client. These data sources are defined on a per-model basis, and you can have as many of them as you like for each model.

Defining Data Sources

By default, each of your models that Coalesce exposes will expose the standard data source (IntelliTect.Coalesce.StandardDataSource<T, TContext>). This data source provides all the standard functionality one would expect - paging, sorting, searching, filtering, and so on. Each of these component pieces is implemented in one or more virtual methods, making the StandardDataSource a great place to start from when implementing your own data source. To suppress this behavior of always exposing the raw StandardDataSource, create your own custom data source and annotate it with [DefaultDataSource].

To implement your own custom data source, you simply need to define a class that implements IntelliTect.Coalesce.IDataSource<T>. To expose your data source to Coalesce, either place it as a nested class of the type T that you data source serves, or annotate it with the [Coalesce] attribute. Of course, the easiest way to create a data source that doesn't require you to re-engineer a great deal of logic would be to inherit from IntelliTect.Coalesce.StandardDataSource<T, TContext>, and then override only the parts that you need.

c#
public class Person
+{
+    [DefaultDataSource]
+    public class IncludeFamily : StandardDataSource<Person, AppDbContext>
+    {
+        public IncludeFamily(CrudContext<AppDbContext> context) : base(context) { }
+
+        public override IQueryable<Person> GetQuery(IDataSourceParameters parameters) 
+            => Db.People
+            .Where(f => User.IsInRole("Admin") || f.CreatedById == User.GetUserId())
+            .Include(f => f.Parents).ThenInclude(s => s.Parents)
+            .Include(f => f.Cousins).ThenInclude(s => s.Parents);
+    }
+}
+
+[Coalesce]
+public class NamesStartingWithA : StandardDataSource<Person, AppDbContext>
+{
+    public NamesStartingWithA(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override IQueryable<Person> GetQuery(IDataSourceParameters parameters) 
+        => Db.People.Include(f => f.Siblings).Where(f => f.FirstName.StartsWith("A"));
+}

The structure of the IQueryable built by the various methods of StandardDataSource is used to shape and trim the structure of the DTO as it is serialized and sent out to the client. One may also override method IncludeTree GetIncludeTree(IQueryable<Person> query, IDataSourceParameters parameters) to control this explicitly. See Include Tree for more information on how this works.

WARNING

If you create a custom data source that has custom logic for securing your data, be aware that the default implementation of StandardDataSource (or your custom default implementation - see below) is still exposed unless you annotate one of your custom data sources with [DefaultDataSource]. Doing so will replace the default data source with the annotated class for your type T.

Dependency Injection

All data sources are instantiated using dependency injection and your application's IServiceProvider. As a result, you can add whatever constructor parameters you desire to your data sources as long as a value for them can be resolved from your application's services. The single parameter to the StandardDataSource is resolved in this way - the CrudContext<TContext> contains the common set of objects most commonly used, including the DbContext and the ClaimsPrincipal representing the current user.

Consuming Data Sources

`,12),y=s("p",null,[e("The "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html#viewmodels"},"ViewModels"),e(" and "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html#listviewmodels"},"ListViewModels"),e(" each have a property called "),s("code",null,"$dataSource"),e(". This property accepts an instance of a "),s("a",{href:"/Coalesce/stacks/vue/layers/models.html"},"DataSource"),e(" class generated in the "),s("a",{href:"/Coalesce/stacks/vue/layers/models.html"},"Model Layer"),e(".")],-1),u=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"Person"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/models.g'")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},", "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," Person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"DataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"IncludeFamily"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," list"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," Person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"DataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"NamesStartingWith"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")])])])],-1),C=s("p",null,[e("The "),s("a",{href:"/Coalesce/stacks/ko/client/view-model.html"},"TypeScript ViewModels"),e(" and "),s("a",{href:"/Coalesce/stacks/ko/client/list-view-model.html"},"TypeScript ListViewModels"),e(" each have a property called "),s("code",null,"dataSource"),e(". These properties accept an instance of a "),s("code",null,"Coalesce.DataSource"),e(". Generated classes that satisfy this relationship for all the data sources that were defined in C# may be found in the "),s("code",null,"dataSources"),e(" property on an instance of a ViewModel or ListViewModel, or in "),s("code",null,"ListViewModels.DataSources")],-1),h=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Person"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"IncludeFamily"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," list"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"NamesStartingWith"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"();")])])])],-1),m=n(`

Standard Parameters

All methods on IDataSource<T> take a parameter that contains all the client-specified parameters for things paging, searching, sorting, and filtering information. Almost all virtual methods on StandardDataSource are also passed the relevant set of parameters.

Custom Parameters

On any data source that you create, you may add additional properties annotated with [Coalesce] that will then be exposed as parameters to the client. These property parameters are currently restricted to primitives (numeric types, strings) and dates (DateTime, DateTimeOffset). Property parameter primitives may be expanded to allow for more types in the future.

c#
[Coalesce]
+public class NamesStartingWith : StandardDataSource<Person, AppDbContext>
+{
+    public NamesStartingWith(CrudContext<AppDbContext> context) : base(context) { }
+
+    [Coalesce]
+    public string StartsWith { get; set; }
+
+    public override IQueryable<Person> GetQuery(IDataSourceParameters parameters) 
+        => Db.People.Include(f => f.Siblings)
+        .Where(f => string.IsNullOrWhitespace(StartsWith) ? true : f.FirstName.StartsWith(StartsWith));
+}

List Auto-loading

You can setup TypeScript List ViewModels to automatically reload from the server when data source parameters change:

`,7),f=s("p",null,[e("To automatically reload a "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),e(" when data source parameters change, simply use the list's "),s("code",null,"$useAutoLoad"),e(" or "),s("code",null,"$startAutoLoad"),e(" function:")],-1),g=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"Person"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/models.g'"),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'"),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," list"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," Person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"DataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"NamesStartingWith")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$useAutoLoad"),s("span",{style:{color:"#D4D4D4"}},"(); "),s("span",{style:{color:"#6A9955"}},"// When using options API, use $startAutoLoad(this)")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Trigger a reload:")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"startsWith"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Jo"'),s("span",{style:{color:"#D4D4D4"}},";")])])])],-1),b=s("p",null,"The properties created on the TypeScript objects are observables so they may be bound to directly. In order to automatically reload a list when a data source parameter changes, you must explicitly subscribe to it:",-1),A=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," list"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"NamesStartingWith"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"startsWith"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#CE9178"}},'"Jo"'),s("span",{style:{color:"#D4D4D4"}},");")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"subscribe"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"); "),s("span",{style:{color:"#6A9955"}},"// Enables automatic reloading.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"();")])])])],-1),v=n('

Standard Data Source

The standard data sources, IntelliTect.Coalesce.StandardDataSource<T> and its EntityFramework-supporting sibling IntelliTect.Coalesce.StandardDataSource<T, TContext>, contain a significant number of properties and methods that can be utilized and/or overridden at your leisure.

Default Loading Behavior

When an object or list of objects is requested, the default behavior of the the StandardDataSource is to load all of the immediate relationships of the object (parent objects and child collections), as well as the far side of many-to-many relationships. This is performed in StandardDataSource.GetQuery(), so in order to suppress this behavior in a custom data source, don't build you query off of base.GetQuery(), but instead start directly from the DbSet for your entity when building your custom query.

Clients can suppress this per-request by setting includes = "none" on your TypeScript ViewModel or ListViewModel, but note this is not a security mechanism and should only be used to reduce payload size or improve response time.

On the server, you can suppress this behavior by placing [Read(NoAutoInclude = true)] on either an entire class (affecting all navigation properties of that type), or on specific navigation properties. When placed on a entity class that holds sensitive data, this can help ensure you don't accidentally leak records due to forgetting to customize the data sources of the types whose navigation properties reference your sensitive entity.

Properties

The following properties are available for use on the StandardDataSource any any derived instances.

',8),S=s("p",null,"The object passed to the constructor that contains the set of objects needed by the standard data source, and those that are most likely to be used in custom implementations.",-1),E=s("p",null,[e("An instance of the DbContext that contains a "),s("code",null,"DbSet"),e(" for the entity served by the data source.")],-1),T=s("p",null,"The user making the current request.",-1),_=s("p",null,"The max number of search terms to process when interpreting a search term word-by-word. Override by setting a value in the constructor.",-1),w=s("p",null,"The page size to use if none is specified by the client. Override by setting a value in the constructor.",-1),F=n(`

The maximum page size that will be served. By default, client-specified page sizes will be clamped to this value. Override by setting a value in the constructor.

Method Overview

The standard data source contains 19 different methods which can be overridden in your derived class to control its behavior.

These methods often call one another, so overriding one method may cause some other method to no longer be called. The hierarchy of method calls, ignoring any logic or conditions contained within, is as follows:

GetMappedItemAsync
+    GetItemAsync
+        GetQueryAsync
+            GetQuery
+        GetIncludeTree
+    TransformResults
+
+GetMappedListAsync
+    GetListAsync
+        GetQueryAsync
+            GetQuery
+        ApplyListFiltering
+            ApplyListPropertyFilters
+                ApplyListPropertyFilter
+            ApplyListSearchTerm
+        GetListTotalCountAsync
+        ApplyListSorting
+            ApplyListClientSpecifiedSorting
+            ApplyListDefaultSorting
+        ApplyListPaging
+        GetIncludeTree
+    TrimListFields
+    TransformResults
+
+GetCountAsync
+    GetQueryAsync
+        GetQuery
+    ApplyListFiltering
+        ApplyListPropertyFilters
+            ApplyListPropertyFilter
+        ApplyListSearchTerm
+    GetListTotalCountAsync

Method Details

All of the methods outlined above can be overridden. A description of each of the non-interface inner methods is as follows:

`,7),I=n('

The method is the one that you will most commonly be override in order to implement custom query logic. The default implementation of GetQueryAsync simply calls GetQuery - be aware of this in cases of complex overrides/inheritance. From this method, you could:

  • Specify additional query filtering such as row-level security or soft-delete logic. Or, restrict the data source entirely for users or whole roles by returning an empty query.
  • Include additional data using EF's .Include() and .ThenInclude().
  • Add additional edges to the serialized object graph using Coalesce's .IncludedSeparately() and .ThenIncluded().

Note

When GetQuery is overridden, the Default Loading Behavior is overridden as well. To restore this behavior, use the IQueryable<T>.IncludeChildren() extension method to build your query.

',3),P=s("p",null,[e("Allows for explicitly specifying the "),s("a",{href:"/Coalesce/concepts/include-tree.html"},"Include Tree"),e(" that will be used when serializing results obtained from this data source into DTOs. By default, the query that is build up through all the other methods in the data source will be used to build the include tree.")],-1),k=s("p",null,[e("Called by other methods in the standard data source to determine whether or not EF Core async methods will be used to evaluate queries. This may be globally disabled when bugs like "),s("a",{href:"https://github.com/dotnet/SqlClient/issues/593",target:"_blank",rel:"noreferrer"},"https://github.com/dotnet/SqlClient/issues/593"),e(" are present in EF Core.")],-1),x=s("p",null,[e("A simple wrapper that calls "),s("code",null,"ApplyListPropertyFilters"),e(" and "),s("code",null,"ApplyListSearchTerm"),e(".")],-1),q=s("p",null,[e("For each value in "),s("code",null,"parameters.Filter"),e(", invoke "),s("code",null,"ApplyListPropertyFilter"),e(" to apply a filter to the query.")],-1),L=s("p",null,"Given a property and a client-provided string value, perform some filtering on that property.",-1),B=s("ul",null,[s("li",null,"Dates with a time component will be matched exactly."),s("li",null,"Dates with no time component will match any dates that fell on that day."),s("li",null,[e("Strings will match exactly unless an asterisk is found, in which case they will be matched with "),s("code",null,"string.StartsWith"),e(".")]),s("li",null,"Enums will match by string or numeric value. Multiple comma-delimited values will create a filter that will match on any of the provided values."),s("li",null,"Numeric values will match exactly. Multiple comma-delimited values will create a filter that will match on any of the provided values.")],-1),M=s("p",null,[e("Applies filters to the query based on the specified search term. See "),s("a",{href:"/Coalesce/modeling/model-components/attributes/search.html"},"[Search]"),e(" for a detailed look at how searching works in Coalesce.")],-1),Q=s("p",null,[e("If any client-specified sort orders are present, invokes "),s("code",null,"ApplyListClientSpecifiedSorting"),e(". Otherwise, invokes "),s("code",null,"ApplyListDefaultSorting"),e(".")],-1),V=s("p",null,[e("Applies sorting to the query based on sort orders specified by the client. If the client specified "),s("code",null,'"none"'),e(" as the sort field, no sorting will take place.")],-1),G=s("p",null,[e("Applies default sorting behavior to the query, including behavior defined with use of "),s("code",null,"[DefaultOrderBy]"),e(" in C# POCOs, as well as fallback sorting to "),s("code",null,'"Name"'),e(" or primary key properties.")],-1),N=s("p",null,"Applies paging to the query based on incoming parameters. Provides the actual page and pageSize that were used as out parameters.",-1),W=s("p",null,[e("Simple wrapper around invoking "),s("code",null,".Count()"),e(" on a query.")],-1),O=n('

Allows for transformation of a result set after the query has been evaluated. This will be called for both lists of items and for single items. This can be used for populating non-mapped properties on a model, or conditionally loading navigation properties using logic that depends upon the contents of each loaded record.

This method is only called immediately before mapping to a DTO; it does not affect operations that don't involve mapping to a DTO - e.g. when loading the target of a /save operation or when loading the invocation target of an instance method.

See the Security page for an example on how to use TransformResults to apply filtered includes.

Do not use TransformResults to modify any database-mapped scalar properties, since such changes could be inadvertently persisted to the database.

',4),R=n(`

Performs trimming of the fields of the result set based on the parameters given to the data source. Can be overridden to forcibly disable this, override the behavior to always trim specific fields, or any other functionality desired.

Globally Replacing the Standard Data Source

You can, of course, create a custom base data source that all your custom implementations inherit from. But, what if you want to override the standard data source across your entire application, so that StandardDataSource<,> will never be instantiated? You can do that too!

Simply create a class that implements IEntityFrameworkDataSource<,> (the StandardDataSource<,> already does - feel free to inherit from it), then register it at application startup like so:

c#
public class MyDataSource<T, TContext> : StandardDataSource<T, TContext>
+    where T : class
+    where TContext : DbContext
+{
+    public MyDataSource(CrudContext<TContext> context) : base(context)
+    {
+    }
+
+    ...
+}
c#
public void ConfigureServices(IServiceCollection services)
+{
+    services.AddCoalesce(b =>
+    {
+        b.AddContext<AppDbContext>();
+        b.UseDefaultDataSource(typeof(MyDataSource<,>));
+    });
+}

Your custom data source must have the same generic type parameters - <T, TContext>. Otherwise, the Microsoft.Extensions.DependencyInjection service provider won't know how to inject it.

`,7);function j(z,$,U,Y,J,H){const t=r("CodeTabs"),o=r("Prop");return c(),i("div",null,[d,a(t,null,{vue:l(()=>[y,u]),knockout:l(()=>[C,h]),_:1}),m,a(t,null,{vue:l(()=>[f,g]),knockout:l(()=>[b,A]),_:1}),v,a(o,{def:"CrudContext Context"}),S,a(o,{def:"TContext Db"}),E,a(o,{def:"ClaimsPrincipal User"}),T,a(o,{def:"int MaxSearchTerms"}),_,a(o,{def:"int DefaultPageSize"}),w,a(o,{def:"int MaxPageSize"}),F,a(o,{def:`IQueryable GetQuery(IDataSourceParameters parameters); +Task> GetQueryAsync(IDataSourceParameters parameters);`}),I,a(o,{def:"IncludeTree? GetIncludeTree(IQueryable query, IDataSourceParameters parameters)"}),P,a(o,{def:"bool CanEvalQueryAsynchronously(IQueryable query)"}),k,a(o,{def:"IQueryable ApplyListFiltering(IQueryable query, IFilterParameters parameters)"}),x,a(o,{def:"IQueryable ApplyListPropertyFilters(IQueryable query, IFilterParameters parameters)"}),q,a(o,{def:"IQueryable ApplyListPropertyFilter(IQueryable query, PropertyViewModel prop, string value)"}),L,B,a(o,{def:"IQueryable ApplyListSearchTerm(IQueryable query, IFilterParameters parameters)"}),M,a(o,{def:"IQueryable ApplyListSorting(IQueryable query, IListParameters parameters)"}),Q,a(o,{def:"IQueryable ApplyListClientSpecifiedSorting(IQueryable query, IListParameters parameters)"}),V,a(o,{def:"IQueryable ApplyListDefaultSorting(IQueryable query)"}),G,a(o,{def:"IQueryable ApplyListPaging(IQueryable query, IListParameters parameters, int? totalCount, out int page, out int pageSize)"}),N,a(o,{def:"Task GetListTotalCountAsync(IQueryable query, IFilterParameters parameters)"}),W,a(o,{def:`void TransformResults(IReadOnlyList results, IDataSourceParameters parameters); +Task TransformResultsAsync(IReadOnlyList results, IDataSourceParameters parameters);`}),O,a(o,{def:"IList TrimListFields(IList mappedResult, IListParameters parameters)"}),R])}const Z=p(D,[["render",j]]);export{X as __pageData,Z as default}; diff --git a/assets/modeling_model-components_data-sources.md.bsBPSQ6R.lean.js b/assets/modeling_model-components_data-sources.md.bsBPSQ6R.lean.js new file mode 100644 index 000000000..3866a74c9 --- /dev/null +++ b/assets/modeling_model-components_data-sources.md.bsBPSQ6R.lean.js @@ -0,0 +1,31 @@ +import{_ as p,D as r,o as c,c as i,I as a,w as l,R as n,k as s,a as e}from"./chunks/framework.g9eZ-ZSs.js";const X=JSON.parse('{"title":"Data Sources","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/data-sources.md","filePath":"modeling/model-components/data-sources.md"}'),D={name:"modeling/model-components/data-sources.md"},d=n("",12),y=s("p",null,[e("The "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html#viewmodels"},"ViewModels"),e(" and "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html#listviewmodels"},"ListViewModels"),e(" each have a property called "),s("code",null,"$dataSource"),e(". This property accepts an instance of a "),s("a",{href:"/Coalesce/stacks/vue/layers/models.html"},"DataSource"),e(" class generated in the "),s("a",{href:"/Coalesce/stacks/vue/layers/models.html"},"Model Layer"),e(".")],-1),u=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"Person"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/models.g'")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},", "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," Person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"DataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"IncludeFamily"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," list"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," Person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"DataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"NamesStartingWith"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")])])])],-1),C=s("p",null,[e("The "),s("a",{href:"/Coalesce/stacks/ko/client/view-model.html"},"TypeScript ViewModels"),e(" and "),s("a",{href:"/Coalesce/stacks/ko/client/list-view-model.html"},"TypeScript ListViewModels"),e(" each have a property called "),s("code",null,"dataSource"),e(". These properties accept an instance of a "),s("code",null,"Coalesce.DataSource"),e(". Generated classes that satisfy this relationship for all the data sources that were defined in C# may be found in the "),s("code",null,"dataSources"),e(" property on an instance of a ViewModel or ListViewModel, or in "),s("code",null,"ListViewModels.DataSources")],-1),h=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Person"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"IncludeFamily"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," list"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"NamesStartingWith"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"();")])])])],-1),m=n("",7),f=s("p",null,[e("To automatically reload a "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),e(" when data source parameters change, simply use the list's "),s("code",null,"$useAutoLoad"),e(" or "),s("code",null,"$startAutoLoad"),e(" function:")],-1),g=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"Person"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/models.g'"),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'"),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," list"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," PersonListViewModel"),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"const"),s("span",{style:{color:"#4FC1FF"}}," dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"$dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," Person"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"DataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"NamesStartingWith")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$useAutoLoad"),s("span",{style:{color:"#D4D4D4"}},"(); "),s("span",{style:{color:"#6A9955"}},"// When using options API, use $startAutoLoad(this)")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#6A9955"}},"// Trigger a reload:")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"startsWith"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#CE9178"}},'"Jo"'),s("span",{style:{color:"#D4D4D4"}},";")])])])],-1),b=s("p",null,"The properties created on the TypeScript objects are observables so they may be bound to directly. In order to automatically reload a list when a data source parameter changes, you must explicitly subscribe to it:",-1),A=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," list"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ListViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"PersonList"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSources"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"NamesStartingWith"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"startsWith"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#CE9178"}},'"Jo"'),s("span",{style:{color:"#D4D4D4"}},");")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"subscribe"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"); "),s("span",{style:{color:"#6A9955"}},"// Enables automatic reloading.")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#9CDCFE"}},"dataSource"),s("span",{style:{color:"#D4D4D4"}},";")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"list"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"();")])])])],-1),v=n("",8),S=s("p",null,"The object passed to the constructor that contains the set of objects needed by the standard data source, and those that are most likely to be used in custom implementations.",-1),E=s("p",null,[e("An instance of the DbContext that contains a "),s("code",null,"DbSet"),e(" for the entity served by the data source.")],-1),T=s("p",null,"The user making the current request.",-1),_=s("p",null,"The max number of search terms to process when interpreting a search term word-by-word. Override by setting a value in the constructor.",-1),w=s("p",null,"The page size to use if none is specified by the client. Override by setting a value in the constructor.",-1),F=n("",7),I=n("",3),P=s("p",null,[e("Allows for explicitly specifying the "),s("a",{href:"/Coalesce/concepts/include-tree.html"},"Include Tree"),e(" that will be used when serializing results obtained from this data source into DTOs. By default, the query that is build up through all the other methods in the data source will be used to build the include tree.")],-1),k=s("p",null,[e("Called by other methods in the standard data source to determine whether or not EF Core async methods will be used to evaluate queries. This may be globally disabled when bugs like "),s("a",{href:"https://github.com/dotnet/SqlClient/issues/593",target:"_blank",rel:"noreferrer"},"https://github.com/dotnet/SqlClient/issues/593"),e(" are present in EF Core.")],-1),x=s("p",null,[e("A simple wrapper that calls "),s("code",null,"ApplyListPropertyFilters"),e(" and "),s("code",null,"ApplyListSearchTerm"),e(".")],-1),q=s("p",null,[e("For each value in "),s("code",null,"parameters.Filter"),e(", invoke "),s("code",null,"ApplyListPropertyFilter"),e(" to apply a filter to the query.")],-1),L=s("p",null,"Given a property and a client-provided string value, perform some filtering on that property.",-1),B=s("ul",null,[s("li",null,"Dates with a time component will be matched exactly."),s("li",null,"Dates with no time component will match any dates that fell on that day."),s("li",null,[e("Strings will match exactly unless an asterisk is found, in which case they will be matched with "),s("code",null,"string.StartsWith"),e(".")]),s("li",null,"Enums will match by string or numeric value. Multiple comma-delimited values will create a filter that will match on any of the provided values."),s("li",null,"Numeric values will match exactly. Multiple comma-delimited values will create a filter that will match on any of the provided values.")],-1),M=s("p",null,[e("Applies filters to the query based on the specified search term. See "),s("a",{href:"/Coalesce/modeling/model-components/attributes/search.html"},"[Search]"),e(" for a detailed look at how searching works in Coalesce.")],-1),Q=s("p",null,[e("If any client-specified sort orders are present, invokes "),s("code",null,"ApplyListClientSpecifiedSorting"),e(". Otherwise, invokes "),s("code",null,"ApplyListDefaultSorting"),e(".")],-1),V=s("p",null,[e("Applies sorting to the query based on sort orders specified by the client. If the client specified "),s("code",null,'"none"'),e(" as the sort field, no sorting will take place.")],-1),G=s("p",null,[e("Applies default sorting behavior to the query, including behavior defined with use of "),s("code",null,"[DefaultOrderBy]"),e(" in C# POCOs, as well as fallback sorting to "),s("code",null,'"Name"'),e(" or primary key properties.")],-1),N=s("p",null,"Applies paging to the query based on incoming parameters. Provides the actual page and pageSize that were used as out parameters.",-1),W=s("p",null,[e("Simple wrapper around invoking "),s("code",null,".Count()"),e(" on a query.")],-1),O=n("",4),R=n("",7);function j(z,$,U,Y,J,H){const t=r("CodeTabs"),o=r("Prop");return c(),i("div",null,[d,a(t,null,{vue:l(()=>[y,u]),knockout:l(()=>[C,h]),_:1}),m,a(t,null,{vue:l(()=>[f,g]),knockout:l(()=>[b,A]),_:1}),v,a(o,{def:"CrudContext Context"}),S,a(o,{def:"TContext Db"}),E,a(o,{def:"ClaimsPrincipal User"}),T,a(o,{def:"int MaxSearchTerms"}),_,a(o,{def:"int DefaultPageSize"}),w,a(o,{def:"int MaxPageSize"}),F,a(o,{def:`IQueryable GetQuery(IDataSourceParameters parameters); +Task> GetQueryAsync(IDataSourceParameters parameters);`}),I,a(o,{def:"IncludeTree? GetIncludeTree(IQueryable query, IDataSourceParameters parameters)"}),P,a(o,{def:"bool CanEvalQueryAsynchronously(IQueryable query)"}),k,a(o,{def:"IQueryable ApplyListFiltering(IQueryable query, IFilterParameters parameters)"}),x,a(o,{def:"IQueryable ApplyListPropertyFilters(IQueryable query, IFilterParameters parameters)"}),q,a(o,{def:"IQueryable ApplyListPropertyFilter(IQueryable query, PropertyViewModel prop, string value)"}),L,B,a(o,{def:"IQueryable ApplyListSearchTerm(IQueryable query, IFilterParameters parameters)"}),M,a(o,{def:"IQueryable ApplyListSorting(IQueryable query, IListParameters parameters)"}),Q,a(o,{def:"IQueryable ApplyListClientSpecifiedSorting(IQueryable query, IListParameters parameters)"}),V,a(o,{def:"IQueryable ApplyListDefaultSorting(IQueryable query)"}),G,a(o,{def:"IQueryable ApplyListPaging(IQueryable query, IListParameters parameters, int? totalCount, out int page, out int pageSize)"}),N,a(o,{def:"Task GetListTotalCountAsync(IQueryable query, IFilterParameters parameters)"}),W,a(o,{def:`void TransformResults(IReadOnlyList results, IDataSourceParameters parameters); +Task TransformResultsAsync(IReadOnlyList results, IDataSourceParameters parameters);`}),O,a(o,{def:"IList TrimListFields(IList mappedResult, IListParameters parameters)"}),R])}const Z=p(D,[["render",j]]);export{X as __pageData,Z as default}; diff --git a/assets/modeling_model-components_methods.md._iwSjDHF.js b/assets/modeling_model-components_methods.md._iwSjDHF.js new file mode 100644 index 000000000..95ff3ef51 --- /dev/null +++ b/assets/modeling_model-components_methods.md._iwSjDHF.js @@ -0,0 +1,134 @@ +import{_ as l,D as t,o as p,c as r,I as c,w as a,R as n,k as s,a as e}from"./chunks/framework.g9eZ-ZSs.js";const B=JSON.parse('{"title":"Methods","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/methods.md","filePath":"modeling/model-components/methods.md"}'),i={name:"modeling/model-components/methods.md"},D=n(`

Methods

Any public methods annotated with the [Coalesce] attribute that are placed on your model classes will have API endpoints and Typescript generated by Coalesce. Both instance methods and static methods are supported. Additionally, any instance methods on Services will also have API endpoints and TypeScript generated.

These custom methods allow you to implement any custom server-side functionality in your Coalesce application that falls outside of the standard CRUD functions that are generated for your entities.

Declaring Methods

Instance Methods

Instance Methods can be declared on your Entity classes. For example:

c#
public class User
+{
+    public int UserId { get; set; }
+
+    public string Email { get; set; }
+
+    [Coalesce]
+    public async Task<ItemResult> SendMessage(
+        [Inject] SmtpClient client,
+        ClaimsPrincipal sender,
+        string message
+    ) {
+        if (string.IsNullOrWhitespace(Email)) return "Recipient has no email";
+        if (string.IsNullOrWhitespace(message)) return "Message is required";
+
+        await client.SendMailAsync(new MailMessage(  
+            from: sender.GetEmailAddress(),
+            to: Email,
+            subject: "Message from MyApp",  
+            body: message
+        ));
+        return true;
+    }
+}

When an instance method is invoked, the target model instance will be loaded using the data source specified by an attribute [LoadFromDataSource(typeof(MyDataSource))] if present. Otherwise, the model instance will be loaded using the default data source for the model's type. If you have a Custom Data Source annotated with [DefaultDataSource], that data source will be used. Otherwise, the Standard Data Source will be used. The consequence of this is that a user cannot call a method on an instance of entity that they're not allowed to see or load.

Instance methods are generated onto the TypeScript ViewModels.

When should I use Instance Methods?

Instance methods, as opposed to static or service methods, are a good fit when implementing an action that directly acts on or depends upon a specific instance of one of your entity types. One of their biggest benefits is the automatic row-level security from data sources as described above.

Static Methods

Static Methods can be declared on your Entity classes. For example:

c#
public class Person 
+{
+    public int PersonId { get; set; }
+
+    public string FirstName { get; set; }
+
+    [Coalesce]
+    public static ICollection<string> NamesStartingWith(
+        AppDbContext db,
+        string characters 
+    ) {
+        return db.People
+            .Select(p => p.FirstName)
+            .Where(f => f.StartsWith(characters))
+            .ToList();
+    }
+}

Static methods are generated onto the TypeScript ListViewModels. All of the same members that are generated for instance methods are also generated for static methods.

When should I use Static Methods?

Static methods are a good fit for actions that don't operate on a specific instance of an entity type, but whose functionality is still closely coupled with a specific, concrete entity type.

For example, imagine you have a File entity class. You could make a static method on that class that accepts a file as a parameter. This method would persist that file to storage and then save a new entity to the database. You would then disable Create on that entity, since the default /save endpoint cannot accept file uploads.

Or, imagine an Invoice class. You might make a static method that returns a summary of sales information for a given time range. Since this summarization would be performing aggregate functions against your Invoice entities and is therefore tightly coupled to Invoices, a static method would be suitable.

Service Methods

Service methods can be declared on a Coalesce Service class:

c#
[Coalesce, Service]
+public class MyService 
+{
+  [Coalesce]
+  public string MyServiceMethod() => "Hello, World!";
+}

Or, they can be declared via a Coalesce Service interface that has an implementation registered with dependency injection:

c#
[Coalesce, Service]
+public interface IMyService 
+{
+  string MyServiceMethod() => "Hello, World!";
+}

When declaring service methods by interface, a [Coalesce] attribute on each method is not needed - the entire interface is exposed by Coalesce.

When should I use Service Methods?

Services are a catch-all feature and can be used for almost any conceivable purpose in Coalesce to implement custom functionality that needs to be invoked by your front-end app.

However, there are some reasons why you might not want to use a service:

  • If the method logically operates on a single entity instance, and/or if using an instance method would let you utilize the row-level security already implemented by one of your data sources to authorize who can invoke the method.
  • If the service would only have one or two methods and would logically make sense as a static or instance method. In other words, if adding a new service class would be detrimental to the organization of your codebase and create "file sprawl".

On the other hand, services have some benefits that instance and static methods cannot provide:

  • Coalesce Services can be declared with an interface, rather than a concrete type, allowing for their implementation to be substituted more easily. For example, a service providing an external integration that you want to mock or stub during automated testing and/or local development.

Parameters

The following parameters can be added to your methods:

TypeDescription

Primitives, Dates, and other Scalars

Most common built-in primitive and scalar data types (numerics, strings, booleans, enums, DateTime, DateTimeOffset), and their nullable variants, are accepted as parameters to be passed from the client to the method call.

Entity Models

When invoking the method on the client, the object's properties will only be serialized one level deep. If an entity model parameter has additional child object properties, they will not be included in the invocation of the method - only the object's primitive & date properties will be deserialized from the client.

External Types

Unlike entity model parameters, external type parameters will be serialized and sent by the client to an arbitrarily deep level, excluding any entity model properties that may be nested inside an external type.

Files

Methods can accept file uploads by using a parameter of type IntelliTect.Coalesce.Models.IFile (or any derived type, like IntelliTect.Coalesce.Models.File).

ICollection<T>, IEnumerable<T>

Collections of any of the above valid parameter types above are also valid parameter types.

DbContext

If the method has a parameter assignable to Microsoft.EntityFrameworkCore.DbContext, then the parameter will be implicitly [Inject]ed.

ClaimsPrincipal

If the method has a parameter of type ClaimsPrincipal, the value of HttpContext.User will be passed to the parameter.

[Inject]

If a parameter is marked with the [Inject] attribute, it will be injected from the application's IServiceProvider.

out IncludeTree

Deprecated. If you need to return an Include Tree to shape the serialization of the method's return value, you should use an ItemResult<T> return value and populate the IncludeTree property on the ItemResult object.

Return Values

You can return virtually anything from these methods:

TypeDescription

Primitives, Dates, and other Scalars

Most common built-in primitive and scalar data types (numerics, strings, booleans, enums, DateTime, DateTimeOffset), and their nullable variants, may be returned from methods.

Entity Models

Any of the types of your models may be returned.

External Types

Any External Types you define may also be returned from a method.

When returning custom types from methods, be careful of the types of their properties. Coalesce will recursively discover and generate code for all public properties of your External Types. If you accidentally include a type that you do not own, these generated types could get out of hand extremely quickly.

Mark any properties you don't want generated with the [InternalUse] attribute, or give them a non-public access modifier. Whenever possible, don't return types that you don't own or control.

ICollection<T>, IEnumerable<T>

Collections of any of the above valid return types above are also valid return types. IEnumerables are useful for generator functions using yield. ICollection is highly suggested over IEnumerable whenever appropriate, though.

IQueryable<T>

Queryables of the valid return types above are valid return types. The query will be evaluated, and Coalesce will attempt to pull an Include Tree from the queryable to shape the response.

When Include Tree functionality is needed to shape the response but an IQueryable<> return type is not feasible, an ItemResult return value with an IncludeTree set on it will do the trick as well.

Files

Methods can return file downloads using type IntelliTect.Coalesce.Models.IFile (or any derived type, like IntelliTect.Coalesce.Models.File).

Please see the File Downloads section below for more details

ItemResult<T>, ItemResult, ListResult<T>

An IntelliTect.Coalesce.Models.ItemResult<T> of any of the valid return types above, including collections, is valid, as well as its non-generic variant ItemResult, and its list variant ListResult<T>.

Use an ItemResult whenever you might need to signal failure and return an error message from a custom method. The WasSuccessful and Message properties on the result object will be sent along to the client to indicate success or failure of the method. The type T will be mapped to the appropriate DTO object before being serialized as normal.

An Include Tree can be set on the object's IncludeTree parameter to shape the serialization of the method's returned value.

Security

You can implement role-based security on a method by placing the [Execute] on the method. Placing this attribute on the method with no roles specified will simply require that the calling user be authenticated.

Security for instance methods is also controlled by the data source that loads the instance - if the data source can't provide an instance of the requested model, the method won't be executed.

See the Security page to read more about custom method security, as well as all other security mechanisms in Coalesce.

Generated TypeScript

See API Callers and ViewModel Layer for details on the code that is generated for your custom methods.

Note

Any Task-returning methods with "Async" as a suffix to the C# method's name will have the "Async" suffix stripped from the generated Typescript.

Method Annotations

Methods can be annotated with attributes to control API exposure and TypeScript generation. The following attributes are available for model methods. General annotations can be found on the Attributes page.

[Coalesce]

The [Coalesce] attribute causes the method to be exposed via a generated API controller. This is not needed for methods defined on an interface marked with [Service] - Coalesce assumes that all methods on the interface are intended to be exposed. If this is not desired, create a new, more restricted interface with only the desired methods to be exposed.

[ControllerAction(Method = HttpMethod, VaryByProperty = string)]

The [ControllerAction] attribute controls how this method is exposed via HTTP. Can be used to customize the HTTP method/verb for the method, as well as caching behavior.

[Execute(string roles)]

The [Execute] attribute specifies which roles can execute this method from the generated API controller. Additional security restrictions that cannot be implemented with roles should be enforced with custom code in the method's implementation.

[LoadFromDataSource(Type dataSourceType)]

The [LoadFromDataSource] attribute specifies that the targeted model instance method should load the instance it is called on from the specified data source when invoked from an API endpoint. If not defined, the model's default data source is used.

File Downloads

Coalesce supports exposing file downloads via custom methods. Simply return a IntelliTect.Coalesce.Models.IFile (or any derived type, like IntelliTect.Coalesce.Models.File), or an ItemResult<> of such.

Consuming file downloads

There are a few conveniences for easily consuming downloaded files from your custom pages.

`,58),d=s("p",null,[e("The "),s("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#api-callers"},"API Callers"),e(" have a property "),s("code",null,"url"),e(". This can be provided directly to your HTML template, with the browser invoking the endpoint automatically.")],-1),y=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")])])])],-1),h=s("div",{class:"language-html"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"html"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#808080"}},"<"),s("span",{style:{color:"#569CD6"}},"img"),s("span",{style:{color:"#9CDCFE"}}," :src"),s("span",{style:{color:"#D4D4D4"}},"="),s("span",{style:{color:"#CE9178"}},'"downloadPicture.url"'),s("span",{style:{color:"#808080"}},">")])])])],-1),C=s("hr",null,null,-1),u=s("p",null,[e("Alternatively, the "),s("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#api-callers"},"API Callers"),e(" for file-returning methods have a method "),s("code",null,"getResultObjectUrl(vue)"),e(". If the method was invoked programmatically (i.e. via "),s("code",null,"caller()"),e(", "),s("code",null,"caller.invoke()"),e(", or "),s("code",null,"caller.invokeWithArgs()"),e("), this method returns an "),s("a",{href:"https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL",target:"_blank",rel:"noreferrer"},"Object URL"),e(" that can be set as the "),s("code",null,"src"),e(" of an "),s("code",null,"image"),e(" or "),s("code",null,"video"),e(" HTML tag.")],-1),m=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"downloadPicture"),s("span",{style:{color:"#D4D4D4"}},"();")])])])],-1),b=s("div",{class:"language-html"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"html"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#808080"}},"<"),s("span",{style:{color:"#569CD6"}},"img"),s("span",{style:{color:"#9CDCFE"}}," :src"),s("span",{style:{color:"#D4D4D4"}},"="),s("span",{style:{color:"#CE9178"}},'"downloadPicture.getResultObjectUrl()"'),s("span",{style:{color:"#808080"}},">")])])])],-1),g=s("p",null,[e("The "),s("a",{href:"/Coalesce/stacks/ko/client/methods.html"},"TypeScript Method Objects"),e(" for HTTP GET methods have a property "),s("code",null,"url"),e(". This can be provided directly to your HTML, with the browser invoking the endpoint as normal.")],-1),f=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Person"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")])])])],-1),v=s("div",{class:"language-html"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"html"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#808080"}},"<"),s("span",{style:{color:"#569CD6"}},"img"),s("span",{style:{color:"#9CDCFE"}}," data-bind"),s("span",{style:{color:"#D4D4D4"}},"="),s("span",{style:{color:"#CE9178"}},'"attr: {src: downloadPicture.url }"'),s("span",{style:{color:"#808080"}},">")])])])],-1),E=s("hr",null,null,-1),w=s("p",null,[e("Alternatively, the "),s("a",{href:"/Coalesce/stacks/ko/client/methods.html"},"TypeScript Method Objects"),e(" for file-returning methods have a property "),s("code",null,"resultObjectUrl"),e(". If the method is invoked programmatically (i.e. via "),s("code",null,".invoke()"),e(" or "),s("code",null,".invokeWithArgs()"),e("), this property contains an "),s("a",{href:"https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL",target:"_blank",rel:"noreferrer"},"Object URL"),e(" that can be set as the "),s("code",null,"src"),e(" of an "),s("code",null,"image"),e(" or "),s("code",null,"video"),e(" HTML tag.")],-1),A=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Person"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},", () "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"downloadPicture"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"invoke"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")])])])],-1),F=s("div",{class:"language-html"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"html"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#808080"}},"<"),s("span",{style:{color:"#569CD6"}},"img"),s("span",{style:{color:"#9CDCFE"}}," data-bind"),s("span",{style:{color:"#D4D4D4"}},"="),s("span",{style:{color:"#CE9178"}},'"attr: {src: downloadPicture.resultObjectUrl }"'),s("span",{style:{color:"#808080"}},">")])])])],-1),T=n(`

Database-stored Files

When storing large byte[] objects in your EF models, it is important that these are never loaded unless necessary. Loading these can cause significant garbage collector churn, or even bring your app to a halt. To achieve this with EF, you can either utilize Table Splitting, or you can use an entire dedicated table that only contains a primary key and the binary content, and nothing else.

WARNING

Storing large binary objects in relational databases comes with significant drawbacks. For large-volume cloud solutions, it is much more costly than dedicated cloud-native file storage like Azure Storage or S3. Also of note is that the larger a database is, the more difficult its backup process becomes.

For files that are stored in your database, Coalesce supports a pattern that allows the file to be streamed directly to the HTTP response without needing to allocate a chunk of memory for the whole file at once. Simply pass an EF IQueryable<byte[]> to the constructor of IntelliTect.Coalesce.Models.File. This implementation, however, is specific to the underlying EF database provider. Currently, only SQL Server and SQLite are supported. Please open a Github issue to request support for other providers. An example of this mechanism is included in the DownloadAttachment method in the code sample below.

The following is an example of utilizing Table Splitting for database-stored files. Generally speaking, metadata about the file should be stored on the "main" entity, and only the bytes of the content should be split into a separate entity.

c#
public class AppDbContext : DbContext
+{
+    public DbSet<Case> Cases { get; set; }
+
+    protected override void OnModelCreating(ModelBuilder modelBuilder)
+    {
+        modelBuilder
+            .Entity<Case>()
+            .ToTable("Cases")
+            .HasOne(c => c.AttachmentContent)
+            .WithOne()
+            .HasForeignKey<CaseAttachmentContent>(c => c.CaseId);
+        modelBuilder
+            .Entity<CaseAttachmentContent>()
+            .ToTable("Cases")
+            .HasKey(d => d.CaseId);
+    }
+}
+
+public class Case
+{
+    public int CaseId { get; set; }
+
+    [Read]
+    public string AttachmentName { get; set; }
+
+    [Read]
+    public long AttachmentSize { get; set; }
+
+    [Read]
+    public string AttachmentType { get; set; }
+
+    [Read, MaxLength(32)] // Adjust max length based on chosen hash algorithm.
+    public byte[] AttachmentHash { get; set; } // Could also be a base64 string if so desired.
+
+    [InternalUse]
+    public CaseAttachmentContent AttachmentContent { get; set; } = new();
+
+    [Coalesce]
+    public async Task UploadAttachment(AppDbContext db, IFile file)
+    {
+        if (file.Content == null) return;
+
+        var content = new byte[file.Length];
+        await file.Content.ReadAsync(content.AsMemory());
+
+        AttachmentContent = new () { CaseId = CaseId, Content = content };
+        AttachmentName = file.Name;
+        AttachmentSize = file.Length;
+        AttachmentType = file.ContentType;
+        AttachmentHash = SHA256.HashData(content);
+    }
+
+    [Coalesce]
+    [ControllerAction(HttpMethod.Get, VaryByProperty = nameof(AttachmentHash))]
+    public IFile DownloadAttachment(AppDbContext db)
+    {
+        return new IntelliTect.Coalesce.Models.File(db.Cases
+            .Where(c => c.CaseId == this.CaseId)
+            .Select(c => c.AttachmentContent.Content)
+        )
+        {
+            Name = AttachmentName,
+            ContentType = AttachmentType,
+        };
+    }
+}
+
+public class CaseAttachmentContent
+{
+    public int CaseId { get; set; }
+
+    [Required]
+    public byte[] Content { get; set; }
+}

Other File Storage

For any other storage mechanism, implementations are similar to the database storage approach above. However, instead of table splitting or using a whole separate table, the file contents are simply stored elsewhere. Continue storing metadata about the file on the primary entity, and implement upload/download methods as desired that wrap the storage provider.

For downloads, prefer directly providing the underlying Stream to the IFile versus wrapping a byte[] in a MemoryStream. This will reduce server memory usage and garbage collector churn.

For cloud storage providers where complex security logic is not needed, consider having clients consume the URL of the cloud resource directly rather than passing the file content through your own server.

`,10);function k(I,S,M,_,q,x){const o=t("CodeTabs");return p(),r("div",null,[D,c(o,null,{vue:a(()=>[d,y,h,C,u,m,b]),knockout:a(()=>[g,f,v,E,w,A,F]),_:1}),T])}const R=l(i,[["render",k]]);export{B as __pageData,R as default}; diff --git a/assets/modeling_model-components_methods.md._iwSjDHF.lean.js b/assets/modeling_model-components_methods.md._iwSjDHF.lean.js new file mode 100644 index 000000000..32cf23d07 --- /dev/null +++ b/assets/modeling_model-components_methods.md._iwSjDHF.lean.js @@ -0,0 +1,12 @@ +import{_ as l,D as t,o as p,c as r,I as c,w as a,R as n,k as s,a as e}from"./chunks/framework.g9eZ-ZSs.js";const B=JSON.parse('{"title":"Methods","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/methods.md","filePath":"modeling/model-components/methods.md"}'),i={name:"modeling/model-components/methods.md"},D=n("",58),d=s("p",null,[e("The "),s("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#api-callers"},"API Callers"),e(" have a property "),s("code",null,"url"),e(". This can be provided directly to your HTML template, with the browser invoking the endpoint automatically.")],-1),y=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")])])])],-1),h=s("div",{class:"language-html"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"html"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#808080"}},"<"),s("span",{style:{color:"#569CD6"}},"img"),s("span",{style:{color:"#9CDCFE"}}," :src"),s("span",{style:{color:"#D4D4D4"}},"="),s("span",{style:{color:"#CE9178"}},'"downloadPicture.url"'),s("span",{style:{color:"#808080"}},">")])])])],-1),C=s("hr",null,null,-1),u=s("p",null,[e("Alternatively, the "),s("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#api-callers"},"API Callers"),e(" for file-returning methods have a method "),s("code",null,"getResultObjectUrl(vue)"),e(". If the method was invoked programmatically (i.e. via "),s("code",null,"caller()"),e(", "),s("code",null,"caller.invoke()"),e(", or "),s("code",null,"caller.invokeWithArgs()"),e("), this method returns an "),s("a",{href:"https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL",target:"_blank",rel:"noreferrer"},"Object URL"),e(" that can be set as the "),s("code",null,"src"),e(" of an "),s("code",null,"image"),e(" or "),s("code",null,"video"),e(" HTML tag.")],-1),m=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"import"),s("span",{style:{color:"#D4D4D4"}}," { "),s("span",{style:{color:"#9CDCFE"}},"PersonViewModel"),s("span",{style:{color:"#D4D4D4"}}," } "),s("span",{style:{color:"#C586C0"}},"from"),s("span",{style:{color:"#CE9178"}}," '@/viewmodels.g'")]),e(` +`),s("span",{class:"line"}),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#DCDCAA"}}," PersonViewModel"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"$load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#C586C0"}},"await"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"downloadPicture"),s("span",{style:{color:"#D4D4D4"}},"();")])])])],-1),b=s("div",{class:"language-html"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"html"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#808080"}},"<"),s("span",{style:{color:"#569CD6"}},"img"),s("span",{style:{color:"#9CDCFE"}}," :src"),s("span",{style:{color:"#D4D4D4"}},"="),s("span",{style:{color:"#CE9178"}},'"downloadPicture.getResultObjectUrl()"'),s("span",{style:{color:"#808080"}},">")])])])],-1),g=s("p",null,[e("The "),s("a",{href:"/Coalesce/stacks/ko/client/methods.html"},"TypeScript Method Objects"),e(" for HTTP GET methods have a property "),s("code",null,"url"),e(". This can be provided directly to your HTML, with the browser invoking the endpoint as normal.")],-1),f=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Person"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},");")])])])],-1),v=s("div",{class:"language-html"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"html"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#808080"}},"<"),s("span",{style:{color:"#569CD6"}},"img"),s("span",{style:{color:"#9CDCFE"}}," data-bind"),s("span",{style:{color:"#D4D4D4"}},"="),s("span",{style:{color:"#CE9178"}},'"attr: {src: downloadPicture.url }"'),s("span",{style:{color:"#808080"}},">")])])])],-1),E=s("hr",null,null,-1),w=s("p",null,[e("Alternatively, the "),s("a",{href:"/Coalesce/stacks/ko/client/methods.html"},"TypeScript Method Objects"),e(" for file-returning methods have a property "),s("code",null,"resultObjectUrl"),e(". If the method is invoked programmatically (i.e. via "),s("code",null,".invoke()"),e(" or "),s("code",null,".invokeWithArgs()"),e("), this property contains an "),s("a",{href:"https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL",target:"_blank",rel:"noreferrer"},"Object URL"),e(" that can be set as the "),s("code",null,"src"),e(" of an "),s("code",null,"image"),e(" or "),s("code",null,"video"),e(" HTML tag.")],-1),A=s("div",{class:"language-ts"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"ts"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#569CD6"}},"var"),s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}}," = "),s("span",{style:{color:"#569CD6"}},"new"),s("span",{style:{color:"#9CDCFE"}}," ViewModels"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"Person"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}},"viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"load"),s("span",{style:{color:"#D4D4D4"}},"("),s("span",{style:{color:"#B5CEA8"}},"1"),s("span",{style:{color:"#D4D4D4"}},", () "),s("span",{style:{color:"#569CD6"}},"=>"),s("span",{style:{color:"#D4D4D4"}}," {")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#9CDCFE"}}," viewModel"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#9CDCFE"}},"downloadPicture"),s("span",{style:{color:"#D4D4D4"}},"."),s("span",{style:{color:"#DCDCAA"}},"invoke"),s("span",{style:{color:"#D4D4D4"}},"();")]),e(` +`),s("span",{class:"line"},[s("span",{style:{color:"#D4D4D4"}},"});")])])])],-1),F=s("div",{class:"language-html"},[s("button",{title:"Copy Code",class:"copy"}),s("span",{class:"lang"},"html"),s("pre",{class:"shiki dark-plus vp-code"},[s("code",null,[s("span",{class:"line"},[s("span",{style:{color:"#808080"}},"<"),s("span",{style:{color:"#569CD6"}},"img"),s("span",{style:{color:"#9CDCFE"}}," data-bind"),s("span",{style:{color:"#D4D4D4"}},"="),s("span",{style:{color:"#CE9178"}},'"attr: {src: downloadPicture.resultObjectUrl }"'),s("span",{style:{color:"#808080"}},">")])])])],-1),T=n("",10);function k(I,S,M,_,q,x){const o=t("CodeTabs");return p(),r("div",null,[D,c(o,null,{vue:a(()=>[d,y,h,C,u,m,b]),knockout:a(()=>[g,f,v,E,w,A,F]),_:1}),T])}const R=l(i,[["render",k]]);export{B as __pageData,R as default}; diff --git a/assets/modeling_model-components_properties.md.gx6u8T0D.js b/assets/modeling_model-components_properties.md.gx6u8T0D.js new file mode 100644 index 000000000..d17d69f2e --- /dev/null +++ b/assets/modeling_model-components_properties.md.gx6u8T0D.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as o,R as a}from"./chunks/framework.g9eZ-ZSs.js";const y=JSON.parse('{"title":"Properties","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/properties.md","filePath":"modeling/model-components/properties.md"}'),r={name:"modeling/model-components/properties.md"},i=a('

Properties

Models in a Coalesce application are just EF Core POCOs. The properties defined on your models should fit within the constraints of EF Core.

Coalesce currently has a few more restrictions than what EF Core allows, but hopefully over time some of these restrictions can be relaxed as Coalesce grows in capability.

Property Varieties

The following kinds of properties may be declared on your models.

Primary Key

To work with Coalesce, your model must have a single property for a primary key. By convention, this property should be named the same as your model class with Id appended to that name, but you can also annotate a property with [Key] or name it exactly "Id" to denote it as the primary key.

Foreign Keys & Reference Navigation Properties

While a foreign key may be declared on your model using only the EF OnModuleBuilding method to specify its purpose, Coalesce won't know what the property is a key for. Therefore, foreign key properties should always be accompanied by a reference navigation property, and vice versa.

In cases where the foreign key is not named after the navigation property with "Id" appended, the [ForeignKeyAttribute] may be used on either the key or the navigation property to denote the other property of the pair, in accordance with the recommendations set forth by EF Core's Modeling Guidelines.

Collection Navigation Properties

Collection navigation properties can be used in a straightforward manner. In the event where the inverse property on the other side of the relationship cannot be determined, [InversePropertyAttribute] will need to be used. EF Core provides documentation on how to use this attribute. Errors will be displayed at generation time if an inverse property cannot be determined without the attribute. We recommend recommended that you declare the type of collection navigation properties as ICollection<T>.

Non-mapped POCOs

Properties of a type that are not on your DbContext will also have corresponding properties generated on the TypeScript ViewModels typed as TypeScript External ViewModels, and the values of such properties will be sent with the object to the client when requested. Properties of this type will also be sent back to the server by the client when they are encountered (currently supported by the Vue Stack only).

See External Types for more information.

Primitives, Scalars, & Dates

Most common built-in primitive and scalar data types (numerics, strings, booleans, enums, DateTime, DateTimeOffset), and their nullable variants, are all supported as model properties.

Getter-only Properties

Any property that only has a getter will also have a corresponding property generated in the TypeScript ViewModels, but won't be sent back to the server during any save actions.

If such a property is defined as an auto-property, the [NotMapped] attribute should be used to prevent EF Core from attempting to map such a property to your database.

Init-only Properties

Properties on Entity Models that use an init accessor rather than a set accessor will be implicitly treated as required, and can also only have a value provided when the entity is created for the first time. Any values provided during save actions for init-only properties when updating an existing entity will be ignored.

Property Customization

For any of the kinds of properties outlined above, the following customizations can be applied:

Attributes

Coalesce provides a number of Attributes, and supports a number of other .NET attributes, that allow for further customization of your model.

Security

Property values received by the server from the client will be ignored if rejected by any property-level Security. This security is implemented in the Generated C# DTOs.

Loading & Serialization

The Default Loading Behavior, any custom functionality defined in Data Sources, and [DtoIncludes] & [DtoExcludes] may also restrict which properties are sent to the client when requested.

NotMapped

While Coalesce does not do anything special for the [NotMapped] attribute, it is still an important attribute to keep in mind while building your model, as it prevents EF Core from doing anything with the property.

',32),n=[i];function s(l,p,d,c,h,m){return t(),o("div",null,n)}const f=e(r,[["render",s]]);export{y as __pageData,f as default}; diff --git a/assets/modeling_model-components_properties.md.gx6u8T0D.lean.js b/assets/modeling_model-components_properties.md.gx6u8T0D.lean.js new file mode 100644 index 000000000..45ad47681 --- /dev/null +++ b/assets/modeling_model-components_properties.md.gx6u8T0D.lean.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as o,R as a}from"./chunks/framework.g9eZ-ZSs.js";const y=JSON.parse('{"title":"Properties","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-components/properties.md","filePath":"modeling/model-components/properties.md"}'),r={name:"modeling/model-components/properties.md"},i=a("",32),n=[i];function s(l,p,d,c,h,m){return t(),o("div",null,n)}const f=e(r,[["render",s]]);export{y as __pageData,f as default}; diff --git a/assets/modeling_model-types_dtos.md.SyTF2MTh.js b/assets/modeling_model-types_dtos.md.SyTF2MTh.js new file mode 100644 index 000000000..eed295a19 --- /dev/null +++ b/assets/modeling_model-types_dtos.md.SyTF2MTh.js @@ -0,0 +1,86 @@ +import{_ as s,o as a,c as n,R as e}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"Custom DTOs","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-types/dtos.md","filePath":"modeling/model-types/dtos.md"}'),o={name:"modeling/model-types/dtos.md"},l=e(`

Custom DTOs

In addition to the generated Generated C# DTOs that Coalesce will create for you, you may also create your own implementations of an IClassDto. These types are first-class citizens in Coalesce - you will get a full suite of features surrounding them as if they were entities. This includes generated API Controllers, Admin Views, and full TypeScript ViewModels and TypeScript List ViewModels.

The difference between a Custom DTO and the underlying entity that they represent is as follows:

  • The only time your custom DTO will be served is when it is requested directly from one of the endpoints on its generated controller, or when its type is explicitly used by a method or property of another type.

  • When mapping data from your database, or mapping data incoming from the client, the DTO itself must manually map all properties, since there is no corresponding Generated DTO. Attributes like [DtoIncludes] & [DtoExcludes] and property-level security through Security Attributes have no effect on custom DTOs, since those attribute only affect what get generated for Generated C# DTOs.

Creating a Custom DTO

To create a custom DTO, define a class annotated with [Coalesce] that implements IClassDTO<T>, where T is an EF Core POCO with a corresponding DbSet<T> on a DbContext that has also been exposed with [Coalesce]. Add any Properties to it just as you would add model properties to a regular EF model. If you are not exposing a DbContext class with [Coalesce] but still wish to create a Custom DTO based upon one of its entities, you can inherit from IClassDTO<T, TContext> instead as a means of explicitly declaring the type of the DbContext.

Next, ensure that one property is annotated with [Key] so that Coalesce can know the primary key of your DTO in order to perform database lookups and keep track of your object uniquely in the client-side TypeScript.

Now, populate the required MapTo and MapFrom methods with code for mapping from and to your DTO, respectively (the methods are named with respect to the underlying entity, not the DTO). Most properties probably map one-to-one in both directions, but you probably created a DTO because you wanted some sort of custom mapping - say, mapping a collection on your entity with a comma-delimited string on the DTO. This is also the place to perform any user-based, role-based, property-level security. You can access the current user on the IMappingContext object.

c#
[Coalesce]
+public class CaseDto : IClassDto<Case>
+{
+    [Key]
+    public int CaseId { get; set; }
+
+    public string Title { get; set; }
+
+    [Read]
+    public string AssignedToName { get; set; }
+
+    public void MapTo(Case obj, IMappingContext context)
+    {
+        obj.Title = Title;
+    }
+
+    public void MapFrom(Case obj, IMappingContext context = null, IncludeTree tree = null)
+    {
+        CaseId = obj.CaseKey;
+        Title = obj.Title;
+        if (obj.AssignedTo != null)
+        {
+            AssignedToName = obj.AssignedTo.Name;
+        }
+    }
+}

WARNING

Custom DTOs do not utilize property-level Security Attributes nor [DtoIncludes] & [DtoExcludes], since these are handled in the Generated DTOs. If you need property-level security or trimming, you must write it yourself in the MapTo and MapFrom methods.

If you have any child objects on your DTO, you can invoke the mapper for some other object using the static Mapper class. Also seen in this example is how to respect the Include Tree when mapping entity types; however, respecting the IncludeTree is optional. Since this DTO is a custom type that you've written, if you're certain your use cases don't need to worry about object graph trimming, then you can ignore the IncludeTree. If you do ignore the IncludeTree, you should pass null to calls to Mapper - don't pass in the incoming IncludeTree, as this could cause unexpected results.

c#
using IntelliTect.Coalesce.Mapping;
+
+[Coalesce]
+public class CaseDto : IClassDto<Case>
+{
+    public int ProductId { get; set; }
+    public Product Product { get; set; }
+    ...
+
+    public void MapFrom(Case obj, IMappingContext context = null, IncludeTree tree = null)
+    {
+        ProductId = obj.ProductId;
+
+        if (tree == null || tree[nameof(this.Product)] != null)
+            Product = Mapper.MapToDto<Product, ProductDtoGen>(obj.Product, context, tree?[nameof(this.Product)]
+        ...
+    }
+}

Using Custom DataSources and Behaviors

Declaring an IClassDto DataSource

When you create a custom DTO, it will use the Standard Data Source and Standard Behaviors just like any of your regular Entity Models. If you wish to override this, your custom data source and/or behaviors MUST be declared in one of the following ways:

  1. As a nested class of the DTO. The relationship between your data source or behaviors and your DTO will be picked up automatically.

    c#
    [Coalesce]
    +public class CaseDto : IClassDto<Case>
    +{
    +    [Key]
    +    public int CaseId { get; set; }
    +
    +    public string Title { get; set; }
    +    
    +    ...
    +
    +    public class MyCaseDtoSource : StandardDataSource<Case, AppDbContext>
    +    {
    +        ...
    +    }
    +}
  2. With a [DeclaredFor] attribute that references the DTO type:

    c#
    [Coalesce]
    +public class CaseDto : IClassDto<Case>
    +{
    +    [Key]
    +    public int CaseId { get; set; }
    +
    +    public string Title { get; set; }
    +    
    +    ...
    +}
    +
    +[Coalesce, DeclaredFor(typeof(CaseDto))]
    +public class MyCaseDtoSource : StandardDataSource<Case, AppDbContext>
    +{
    +    ...
    +}

ProjectedDtoDataSource

In addition to creating a Data Source by deriving from Standard Data Source, there also exists a class ProjectedDtoDataSource that can be used to easily perform projection from EF model types to your custom DTO types using EF query projections. ProjectedDtoDataSource inherits from Standard Data Source.

c#
[Coalesce, DeclaredFor(typeof(CaseDto))]
+public class CaseDtoSource : ProjectedDtoDataSource<Case, CaseDto, AppDbContext>
+{
+    public CaseDtoSource(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override IQueryable<CaseDto> ApplyProjection(IQueryable<Case> query, IDataSourceParameters parameters)
+    {
+        return query.Select(c => new CaseDto
+        {
+            CaseId = c.CaseKey,
+            Title = c.Title,
+            AssignedToName = c.AssignedTo == null ? null : c.AssignedTo.Name
+        });
+    }
+}

Surgical Saves

The Vue ViewModels support surgical saves through their $saveMode property, and Knockout ViewModels through the saveIncludedFields configuration.

Surgical saves require DTOs on the server that are capable of determining which of their properties have been set by the model binder, as surgical saves are sent from the client by entirely omitting properties from the x-www-form-urlencoded body that is sent to the server.

The Generated C# DTOs implement the necessary logic for this; however, any Custom DTOs must have this logic manually written by you, the developer. Either implement the same pattern that can be seen in the Generated C# DTOs, or do not use surgical saves with Custom DTOs.

`,23),p=[l];function t(c,r,D,y,i,C){return a(),n("div",null,p)}const h=s(o,[["render",t]]);export{u as __pageData,h as default}; diff --git a/assets/modeling_model-types_dtos.md.SyTF2MTh.lean.js b/assets/modeling_model-types_dtos.md.SyTF2MTh.lean.js new file mode 100644 index 000000000..9b1230932 --- /dev/null +++ b/assets/modeling_model-types_dtos.md.SyTF2MTh.lean.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as n,R as e}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"Custom DTOs","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-types/dtos.md","filePath":"modeling/model-types/dtos.md"}'),o={name:"modeling/model-types/dtos.md"},l=e("",23),p=[l];function t(c,r,D,y,i,C){return a(),n("div",null,p)}const h=s(o,[["render",t]]);export{u as __pageData,h as default}; diff --git a/assets/modeling_model-types_entities.md.kyLYfGOY.js b/assets/modeling_model-types_entities.md.kyLYfGOY.js new file mode 100644 index 000000000..e1306ab1e --- /dev/null +++ b/assets/modeling_model-types_entities.md.kyLYfGOY.js @@ -0,0 +1,48 @@ +import{_ as s,o as a,c as e,R as n}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"Entity Models","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-types/entities.md","filePath":"modeling/model-types/entities.md"}'),o={name:"modeling/model-types/entities.md"},l=n(`

Entity Models

Models are the core business objects of your application - they serve as the fundamental representation of data in your application. The design of your models is very important. In Entity Framework Core (EF), data models are just Plain Old CLR Objects (POCOs).

Building a Data Model

To start building your data model that Coalesce will generate code for, follow the best practices for EF Core. Guidance on this topic is available in abundance in the Entity Framework Core documentation.

Don't worry about querying or saving data when you're just getting started - Coalesce will provide a lot of that functionality for you, and it is very easy to customize what Coalesce offers later. To get started, just build your POCOs and DbContext classes. Annotate your DbContext class with [Coalesce] so that Coalesce will discover it and generate code based off of your context for you.

Before you start building, you are highly encouraged to read the sections below. The linked pages explain in greater detail what Coalesce will build for you for each part of your data model.

Properties

Read Properties for an outline of the different types of properties that you may place on your models and the code that Coalesce will generate for each of them.

Attributes

Coalesce provides a number of C# attributes that can be used to decorate your model classes and their properties in order to customize behavior, appearance, security, and more. Coalesce also supports a number of annotations from System.ComponentModel.DataAnnotations.

Read Attributes to learn more.

Methods

You can place both static and interface methods on your model classes. Any public methods annotated with [Coalesce] will have a generated API endpoint and corresponding generated TypeScript members for calling this API endpoint. Read Methods to learn more.

Customizing CRUD Operations

Once you've got a solid data model in place, its time to start customizing the way that Coalesce will read your data, as well as the way that it will handle your data when processing creates, updates, and deletes.

Data Sources

The method by which you can control what data the users of your application can access through Coalesce's generated APIs is by creating custom data sources. These are classes that allow complete control over the way that data is retrieved from your database and provided to clients. Read Data Sources to learn more.

Behaviors

Behaviors in Coalesce are to mutating data as data sources are to reading data. Defining a behaviors class for a model allows complete control over the way that Coalesce will create, update, and delete your application's data in response to requests made through its generated API. Read Behaviors to learn more.

Standalone (non-EF) Entities

In Coalesce, Standalone Entities are entity types that are not based on Entity Framework. These types are discovered by Coalesce by annotating them with [Coalesce, StandaloneEntity].

For these types, you must define at least one custom Data Source, and optionally a Behaviors class as well. If no behaviors are defined, the type is implicitly read-only, equivalent to turning off create/edit/delete via the Security Attributes.

To define data sources and behaviors for Standalone Entities, it is recommended you inherit from StandardDataSource<T> and StandardBehaviors<T>, respectively. For example:

c#
[Coalesce, StandaloneEntity]
+public class StandaloneExample
+{
+    public int Id { get; set; }
+
+    [Search(SearchMethod = SearchAttribute.SearchMethods.Contains), ListText]
+    public string Name { get; set; } = "";
+
+    [DefaultOrderBy]
+    public DateTimeOffset Date { get; set; }
+
+    private static int nextId = 0;
+    private static ConcurrentDictionary<int, StandaloneExample> backingStore = new ConcurrentDictionary<int, StandaloneExample>();
+
+    public class DefaultSource : StandardDataSource<StandaloneExample>
+    {
+        public DefaultSource(CrudContext context) : base(context) { }
+
+        public override Task<IQueryable<StandaloneExample>> GetQueryAsync(IDataSourceParameters parameters)
+            => Task.FromResult(backingStore.Values.AsQueryable());
+    }
+
+    public class Behaviors : StandardBehaviors<StandaloneExample>
+    {
+        public Behaviors(CrudContext context) : base(context) { }
+
+        public override Task ExecuteDeleteAsync(StandaloneExample item)
+        {
+            backingStore.TryRemove(item.Id, out _);
+            return Task.CompletedTask;
+        }
+
+        public override Task ExecuteSaveAsync(SaveKind kind, StandaloneExample? oldItem, StandaloneExample item)
+        {
+            if (kind == SaveKind.Create)
+            {
+                item.Id = Interlocked.Increment(ref nextId);
+                backingStore.TryAdd(item.Id, item);
+            }
+            else
+            {
+                backingStore.TryRemove(item.Id, out _);
+                backingStore.TryAdd(item.Id, item);
+            }
+            return Task.CompletedTask;
+        }
+    }
+}

The above example is admittedly contrived, as it is unlikely that you would be using an in-memory collection as a data persistence mechanism. A more likely real-world scenario would be to inject an interface to some other data store. Data Source and Behavior classes are instantiated using your application's service provider, so any registered service can be injected.

`,25),t=[l];function p(r,c,D,i,y,d){return a(),e("div",null,t)}const h=s(o,[["render",p]]);export{u as __pageData,h as default}; diff --git a/assets/modeling_model-types_entities.md.kyLYfGOY.lean.js b/assets/modeling_model-types_entities.md.kyLYfGOY.lean.js new file mode 100644 index 000000000..f67e3f798 --- /dev/null +++ b/assets/modeling_model-types_entities.md.kyLYfGOY.lean.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as e,R as n}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"Entity Models","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-types/entities.md","filePath":"modeling/model-types/entities.md"}'),o={name:"modeling/model-types/entities.md"},l=n("",25),t=[l];function p(r,c,D,i,y,d){return a(),e("div",null,t)}const h=s(o,[["render",p]]);export{u as __pageData,h as default}; diff --git a/assets/modeling_model-types_external-types.md.qdzqqDbb.js b/assets/modeling_model-types_external-types.md.qdzqqDbb.js new file mode 100644 index 000000000..90b0afb08 --- /dev/null +++ b/assets/modeling_model-types_external-types.md.qdzqqDbb.js @@ -0,0 +1,41 @@ +import{_ as s,o as a,c as n,R as l}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"External Types","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-types/external-types.md","filePath":"modeling/model-types/external-types.md"}'),e={name:"modeling/model-types/external-types.md"},p=l(`

External Types

In Coalesce, any type which is connected to your data model but is not directly part of it is considered to be an "external type".

The collection of external types for a data model looks like this:

  1. Take all of the api-served types in your data model. This includes Entity Models and Custom DTOs.
  2. Take all of the property types, method parameters, and method return types of these types.
  3. Any of these types which are not built-in scalar types and not one of the aforementioned api-served types are external types.
  4. For any external type discovered, any of the property types which qualify under the above rules are also external types.

WARNING

Be careful when using types that you do not own for properties and method returns in your data model. When Coalesce generates external type ViewModels and DTOs, it will not stop until it has exhausted all paths that can be reached by following public property types and method returns.

In general, you should only expose types that you have created so that you will always have full control over them. Mark any properties you don't wish to expose with [InternalUse], or make those members non-public.

Generated Code

For each external type found in your application's model, Coalesce will generate:

Example Data Model

For example, in the following scenario, the following classes are considered as external types:

  • PluginMetadata, exposed through a getter-only property on ApplicationPlugin.
  • PluginResult, exposed through a method return on ApplicationPlugin.

PluginHandler is not because it not exposed by the model, neither directly nor through any of the other external types.

c#
public class AppDbContext : DbContext {
+    public DbSet<Application> Applications { get; set; }
+    public DbSet<ApplicationPlugin> ApplicationPlugins { get; set; }
+}
+
+public class Application {
+    public int ApplicationId { get; set; }
+    public string Name { get; set; }
+    public ICollection<ApplicationPlugin> Plugins { get; set; }
+}
+
+public class ApplicationPlugin {
+    public int ApplicationPluginId { get; set; }
+    public int ApplicationId { get; set; }
+    public Application Application { get; set; }
+
+    public string TypeName { get; set; }
+
+    private PluginHandler GetInstance() => 
+        ((PluginHandler)Activator.CreateInstance(Type.GetType(TypeName)));
+
+    public PluginMetadata Metadata => GetInstance().GetMetadata();
+
+    public PluginResult Invoke(string action, string data) => GetInstance().Invoke(Application, action, data);
+}
+
+public abstract class PluginHandler { 
+    public abstract PluginMetadata GetMetadata();
+    public abstract PluginResult Invoke(Application app, string action, string data);
+}
+
+public abstract class PluginMetadata { 
+    public bool Name { get; set; }
+    public string Version { get; set; }
+    public ICollection<string> Actions { get; set; }
+}
+
+public abstract class PluginResult { 
+    public bool Success { get; set; }
+    public string Message { get; set; }
+}

Loading & Serialization

External types have slightly different behavior when undergoing serialization to be sent to the client. Unlike database-mapped types which are subject to the rules of Include Tree, external types ignore the Include Tree when being mapped to DTOs for serialization. Read External Type Caveats for a more detailed explanation of this exception.

`,15),o=[p];function t(c,r,D,y,i,C){return a(),n("div",null,o)}const h=s(e,[["render",t]]);export{u as __pageData,h as default}; diff --git a/assets/modeling_model-types_external-types.md.qdzqqDbb.lean.js b/assets/modeling_model-types_external-types.md.qdzqqDbb.lean.js new file mode 100644 index 000000000..5127b5a87 --- /dev/null +++ b/assets/modeling_model-types_external-types.md.qdzqqDbb.lean.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as n,R as l}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"External Types","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-types/external-types.md","filePath":"modeling/model-types/external-types.md"}'),e={name:"modeling/model-types/external-types.md"},p=l("",15),o=[p];function t(c,r,D,y,i,C){return a(),n("div",null,o)}const h=s(e,[["render",t]]);export{u as __pageData,h as default}; diff --git a/assets/modeling_model-types_services.md.CHVSzKDm.js b/assets/modeling_model-types_services.md.CHVSzKDm.js new file mode 100644 index 000000000..ce427259e --- /dev/null +++ b/assets/modeling_model-types_services.md.CHVSzKDm.js @@ -0,0 +1,27 @@ +import{_ as s,o as e,c as a,R as n}from"./chunks/framework.g9eZ-ZSs.js";const h=JSON.parse('{"title":"Services","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-types/services.md","filePath":"modeling/model-types/services.md"}'),o={name:"modeling/model-types/services.md"},l=n(`

Services

In a Coalesce application, you are likely to end up with a need for some API endpoints that aren't closely tied with your regular data model. While you could stick Static Methods on one of your entities, do so is detrimental to the organization of your code.

Instead, Coalesce allows you to generate API Controllers and a TypeScript client from a service. A service, in this case, is nothing more than a C# class or an interface with methods on it, annotated with [Coalesce, Service]. An implementation of this class or interface must be injectable from your application's service container, so a registration in Startup.cs is needed.

The instance methods of these services work just like other custom Methods in Coalesce, with one notable distinction: Instance methods don't operate on an instance of a model, but instead on a dependency injected instance of the service.

Generated Code

For each external type found in your application's model, Coalesce will generate:

  • An API controller with endpoints that correspond to the service's instance methods.
  • A TypeScript client containing the members outlined in Methods for invoking these endpoints.

Example Service

An example of a service might look something like this:

c#
[Coalesce, Service]
+public interface IWeatherService
+{
+    WeatherData GetWeather(string zipCode);
+}

With an implementation:

c#
public class WeatherService : IWeatherService
+{
+    public WeatherService(AppDbContext db)
+    {
+        this.db = db;
+    }
+
+    public WeatherData GetWeather(string zipCode)
+    {
+        // Assuming some magic HttpGet method that works as follows...
+        var response = HttpGet("http://www.example.com/api/weather/" + zipCode);
+        return response.Body.SerializeTo<WeatherData>();
+    }
+
+    public void MethodThatIsNotExposedBecauseItIsNotOnTheExposedInterface() {  }
+}

And a registration:

c#
public class Startup 
+{
+    public void ConfigureServices(IServiceCollection services)
+    {
+        services.AddCoalesce<AppDbContext>();
+        services.AddScoped<IWeatherService, WeatherService>();
+    }
+}

While it isn't required that an interface for your service exist (you can generate directly from the implementation), it is highly recommended that an interface be used. Interfaces increase testability and reduce risk of accidentally changing the signature of a published API, among other benefits.

`,15),p=[l];function t(c,r,i,D,d,y){return e(),a("div",null,p)}const m=s(o,[["render",t]]);export{h as __pageData,m as default}; diff --git a/assets/modeling_model-types_services.md.CHVSzKDm.lean.js b/assets/modeling_model-types_services.md.CHVSzKDm.lean.js new file mode 100644 index 000000000..77ff63a5e --- /dev/null +++ b/assets/modeling_model-types_services.md.CHVSzKDm.lean.js @@ -0,0 +1 @@ +import{_ as s,o as e,c as a,R as n}from"./chunks/framework.g9eZ-ZSs.js";const h=JSON.parse('{"title":"Services","description":"","frontmatter":{},"headers":[],"relativePath":"modeling/model-types/services.md","filePath":"modeling/model-types/services.md"}'),o={name:"modeling/model-types/services.md"},l=n("",15),p=[l];function t(c,r,i,D,d,y){return e(),a("div",null,p)}const m=s(o,[["render",t]]);export{h as __pageData,m as default}; diff --git a/assets/security-overview.lqdZeXXG.webp b/assets/security-overview.lqdZeXXG.webp new file mode 100644 index 0000000000000000000000000000000000000000..428048aba6ee4ba2acd6ebcf660b267211cb166a GIT binary patch literal 108183 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%0A;m=0v)7T-J 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�rY0)j7v>RpkI?L$p<#7G(nw6$08EyF-fTE z7q9MT>*g%VPg2(i9zDt=)Pe}s=KeJ#AtyvVZZ_x+!!2X-x#=b3GCXF~l6?wE0OEvw zZ`jNIpd^|q^`*)hh7cOiDk-mu+9S~5Uj4VycIh58$#w8WEd_E8K~kiACPxKRCwbQu z4zAbr+mb_*W0q(bx756hG)}XrDxD2 z9q*xMnXtKMq@xy{gJL>=+jf97UBKfl53`)vR0^kQ{fn6wsW;pK z#5ff+1zZo?&1C2VmbsRVOv> zeXCK?5s5Y1w7jPzfL|*7^T`B}eMV^#zHo=OCo_FqyRmbGr)fL@B0XnlWZX&^sgA|> zo@$HUri|g0q9>~!qFC$QhUTckO{*Y2xbt*Gs2EV{)Of34V<(rV8>-{lv&2TatlH`x z3D9Iqd!;dCl|L4A4ke}^dzVNrl!K?FGK4imFtaz7b8=vcv{r;2qnr$O`>3+VeQDTHjBl5~S^gyJ96&ZzN z-!&}#KUJJr+hyAVx;125Nt-c*_=OjA<7YN4*8ES`lG{ht%`5kBy;TCj zYprOa%|V8Renj73Xj`2#+`KR;kdfyrGYYxkbTsZwrnw8ACkJY3@ddw&Cg9&) zMSfzd_S-9vb2t ze?B>hk5?+}4^O#UoiYDTtZ@pvQ|(<02q|0;Q6nrNK@xnk!1}`Wx0wrn70sb0XF6B& zFqoN>Rlgf*SAJ*Ne>vM&kR{JPD{p-!Ota8~{HQPZTr!~kwxMLdetx)BS@J>SXWn$H;x6^^SsP29?eUqKTN2?dKyyKd8ZxM&&pCgjl#M{dl8J;?9<`ilNXRmB_VQ}fG(2DPUxF?4yHk}T^ zUqNk)O02PHm|f~ocr1abj9F^+k#l@PJjT|WI7nx|n@#qmsH)=xVEaEACZ^V)jUvSb7|r$d}m831V<$5Jmw!Lt}FkF+vhcp4UVF3 zZ!}bc$H~)dpZJE};NPrux?+1r z<~HFyRIuK$^`2i@sYMueX?WgKm^mh6xFqv^RzS$U0Un=2%)Yr1xl8+s+{kg$r$xcY<%*}u{!(7GJ@i%I8%cQq?qx~%#C^r zit4JIJoSloPq#AEmC9wI8J?PUAKIkcrF+WV_Nh!o&)fD3^o*$6jTYFXPBEMNs@4`)-;I-v)CWUQAC$2v!z&v0Ma#nln#^klgb zvX?V&X{jfL+lEEN7OzM1JopRUj??Xa79p)k55o;ejCvbn_D<(()+|uY1*|%2HinrG z(x?n^(SShXqnds!j4`b_FSJ;Rt~33lbDJ(+@kvoExwLBANEaYGJ zdM$d&6n>kqD(dZqig({Z0x`~#8h46N(Q;j~0PpXUnO_oxt5F@QU?S-cO(|x+SgwaV zOO?NSXz}yI%6Q6FF5YiK{6SVBM;)IUt?3ZNliEVZgM21ov8eH9jMOOb^49oJp@pX1 z=y{zju!O&Qxpsqd_7r(7WIc4o@l+VwLwZ+BPcSzlq2eQ3IOaj*Mm#_}vOTTdSaSIN z1J_~~hbp{a3Q2RV^ZS<8-V`RGaicr0$qfp!H_Ixxr+hm0S%tw4;8JrhK#$UMm!JB< zweyUuDlE4Og#tx9ivTGERi-G=f3hyx$igs#zaBocf5wx?zW8BJAr1)vqHqXcz#o~z?yFXWnZU8&bX zwe?NW_ULzh_Yb+c7&l{Ppq7rd-{YJ>xZ z=M_uKrNP(9_~-j^i5P!HKW@zSk>oZGxArNuBC({RiHSAf%&GI(MTB4Mmmot7S?MS1f3{U z_$F7^+m)W0^9z2mb;Ex%7xzA3>*>BEnP#|ARj;wy{_O)#;bY~6BxtlW@#evVtd%Ed z%ZSE|;d&oXS+l75jqU!jH#2~lZ|Rx6dUXtd=on2m&|X3|9uF2-U*yVt$udhJD&CX< zx(Gw-^#jNXAF{kD?f)??1>EFtM9*>c2`ox`_hT0)u2^59Vt}UCjSOu)TuD&DSfcnu ze(?-yS_MGZo)xw5+f6kzx(<0jb*zA0h%%4CbQ*2#b(AlSqi!%q2lZQ{?A-_owU*UR zH!w;VdKvkXGsyIj;C=3%)Yr21DfCkYO|Ov(VYVPbgfbg3r4LAQlUyt!RZ#Wxfsc z&_bT+0ud!%(gRh8HiA)V?21djSlZd=j@-F9x=J7qt3+@#>~8uVwD5i`@$#@0Vk`%7 zjE`$YQ#C{}lXa%0zP_{)V2GhaO<%%AyLVxO&gg9Pu3^Xp#t$)(ks6lB(BSS* zrkszcNjqOnA*hPB#~L8Ud}U2zPu~W_Fc1TAvZDgcCm3eZlZ3uy1`iR<R8gyov^a&F;m&89vOhzpPaP19H+LK>{T4qwI$Yh-k(5rx z0knR?Uc~;L?x->i>nI-51F0R`T33q}BWoL?H58Fwuo1ioGUm zAw+u-pEgiq*;T_$ggIdki;lZAbsp3LOx&dK^bw%6KFP=XWHMCs>3RpL=fRfY)9wLA z-;pT;$V1#4NI_ATD2{3wCiiHr`|NqF_<)TB9NiO`#4G^!W@*w42l|s@J^L$zjCwbO z(ERgD)CU3rCT-}b5a#O9{-&L*YZjvgdZ4}ZBU-&zRgAq$D4C?3F0jg;fHo7~k8(Ch zNa>}c`F)p6ve}!{kh!-$yO=Ui{&VmGXJ<{J*B?5n8%h$9;%&){Th?Yljf%!~v659S z0Y3huq>CgT@M@_am5X+0I$Q!x^Q6GhP7m<;k=fyMbI?*-yl;&hE~a|0u?CcJr5 z{<%^9n25TB=*o|mke~vB{PEyGNWh>#z(43e3kw+VU$6RuvHyp4e=x=WVnBW{BQOXM z;*U4|Sr(yS|BZM5!T+&4S74wM(Es!H1{Su?bhakW1T1vSbc~EZKmZ`1I0zuHpI8Pa zW(MHA|Emp>_y6Vk&-I}H!*@Rg0sq54!mGenerated C# DTOs

Data Transfer Objects, or DTOs, allow for transformations of data from the data store into a format more suited for transfer and use on the client side. This often means trimming properties and flattening structures to provide a leaner over-the-wire experience. Coalesce aims to support this as seamlessly as possible.

Coalesce supports two types of DTOs:

  • DTOs that are automatically generated for each POCO database object. These are controlled via Attributes on the POCO. These are outlined below.
  • DTOs that you create with IClassDto. These are outlined at Custom DTOs.

Automatically Generated DTOs

Every class that is exposed through Coalesce's generated API will have a corresponding DTO generated for it. These DTOs are used to shuttle data back and forth to the client. They are generated classes that have nullable versions of all the properties on the POCO class.

[DtoIncludes] & [DtoExcludes] and the Includes String infrastructure can be used to indicate which properties should be transferred to the client in which cases, and Include Tree is used to dictate how these DTOs are constructed from POCOs retrieved from the database.

The [Read] and [Edit] attributes can be used to apply property-level security, which manifests as conditional logic in the mapping methods on the generated DTOs.

See the Security page to read more about property-level security, as well as all other security mechanisms in Coalesce.

',9),l=[r];function n(i,c,d,h,u,p){return t(),a("div",null,l)}const T=e(o,[["render",n]]);export{f as __pageData,T as default}; diff --git a/assets/stacks_agnostic_dtos.md.8_xJeZ2y.lean.js b/assets/stacks_agnostic_dtos.md.8_xJeZ2y.lean.js new file mode 100644 index 000000000..65c498c28 --- /dev/null +++ b/assets/stacks_agnostic_dtos.md.8_xJeZ2y.lean.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,R as s}from"./chunks/framework.g9eZ-ZSs.js";const f=JSON.parse('{"title":"Generated C# DTOs","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/agnostic/dtos.md","filePath":"stacks/agnostic/dtos.md"}'),o={name:"stacks/agnostic/dtos.md"},r=s("",9),l=[r];function n(i,c,d,h,u,p){return t(),a("div",null,l)}const T=e(o,[["render",n]]);export{f as __pageData,T as default}; diff --git a/assets/stacks_agnostic_generation.md.--UgUCa6.js b/assets/stacks_agnostic_generation.md.--UgUCa6.js new file mode 100644 index 000000000..c1c92001f --- /dev/null +++ b/assets/stacks_agnostic_generation.md.--UgUCa6.js @@ -0,0 +1,18 @@ +import{_ as e,o,c as a,R as n}from"./chunks/framework.g9eZ-ZSs.js";const f=JSON.parse('{"title":"Code Generation Overview","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/agnostic/generation.md","filePath":"stacks/agnostic/generation.md"}'),s={name:"stacks/agnostic/generation.md"},t=n(`

Code Generation Overview

Coalesce's principal purpose is a code generation framework for automating the creation of the boring-but-necessary parts of a web application. Below, you find an overview of the different components of Coalesce's code generation features.

Running Code Generation

Coalesce's code generation is ran via a dotnet CLI tool, dotnet coalesce. In order to invoke this tool, you must have the appropriate references to the package that provides it in your .csproj file:

xml
<Project Sdk="Microsoft.NET.Sdk.Web">
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+
+    <!-- Necessary to use DotNetCliToolReference with modern framework versions -->
+    <DotnetCliToolTargetFramework>net7.0</DotnetCliToolTargetFramework>
+  </PropertyGroup>
+
+  ...
+
+  <ItemGroup>
+    <PackageReference Include="IntelliTect.Coalesce" Version="..." />
+  </ItemGroup>
+
+  <ItemGroup>
+    <DotNetCliToolReference Include="IntelliTect.Coalesce.Tools" Version="..." />
+  </ItemGroup>  
+</Project>

CLI Options

All configuration of the way that Coalesce interacts with your projects, including locating, analyzing, and producing generated code, is done in a json configuration file, coalesce.json. Read more about this file at Code Generation Configuration.

There are a couple of extra options which are only available as CLI parameters to dotnet coalesce. These options do not affect the behavior of the code generation - only the behavior of the CLI itself.

--debug - When this flag is specified when running dotnet coalesce, Coalesce will wait for a debugger to be attached to its process before starting code generation.

-v|--verbosity <level> - Set the verbosity of the output. Options are trace, debug, information, warning, error, critical, and none.

Generated Code

Coalesce will generate a full vertical stack of code for you:

Backend C#

API Controllers

For each of your Entity Models, Custom DTOs, and Services, an API controller is created in the /Api/Generated directory of your web project. These controllers provide a number of endpoints for interacting with your data.

These controllers can be secured at a high level using Security Attributes, and when applicable to the type, with Data Sources and Behaviors.

C# DTOs

For each of your Entity Models, a C# DTO class is created. These classes are used to hold the data that will be serialized and sent to the client, as well as data that has been received from the client before it has been mapped back to your EF POCO class.

See Generated C# DTOs for more information.

Frontend - Vue

An overview of the Vue generated code can be found at Vue Overview.

Frontend - Knockout

An overview of the legacy Knockout generated code can be found at Knockout Overview.

`,23),l=[t];function r(c,p,i,d,h,u){return o(),a("div",null,l)}const g=e(s,[["render",r]]);export{f as __pageData,g as default}; diff --git a/assets/stacks_agnostic_generation.md.--UgUCa6.lean.js b/assets/stacks_agnostic_generation.md.--UgUCa6.lean.js new file mode 100644 index 000000000..079423dd3 --- /dev/null +++ b/assets/stacks_agnostic_generation.md.--UgUCa6.lean.js @@ -0,0 +1 @@ +import{_ as e,o,c as a,R as n}from"./chunks/framework.g9eZ-ZSs.js";const f=JSON.parse('{"title":"Code Generation Overview","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/agnostic/generation.md","filePath":"stacks/agnostic/generation.md"}'),s={name:"stacks/agnostic/generation.md"},t=n("",23),l=[t];function r(c,p,i,d,h,u){return o(),a("div",null,l)}const g=e(s,[["render",r]]);export{f as __pageData,g as default}; diff --git a/assets/stacks_agnostic_getting-started-modeling.md.KbmYXIa-.js b/assets/stacks_agnostic_getting-started-modeling.md.KbmYXIa-.js new file mode 100644 index 000000000..83f156957 --- /dev/null +++ b/assets/stacks_agnostic_getting-started-modeling.md.KbmYXIa-.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as o,R as a}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"Data Modeling","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/agnostic/getting-started-modeling.md","filePath":"stacks/agnostic/getting-started-modeling.md"}'),n={name:"stacks/agnostic/getting-started-modeling.md"},i=a('

Data Modeling

At this point, you can open up the newly-created solution in Visual Studio and run your application. However, your application won't do much without a data model, so you will probably want to do the following before running:

  • Create an initial Data Model by adding EF entity classes to the data project and the corresponding DbSet<> properties to AppDbContext. You will notice that the starter project includes a single model, Widget, to start with. Feel free to change this model or remove it entirely. Read Entity Models for more information about creating a data model.

  • Run dotnet ef migrations add Init (Init can be any name) in the data project to create an initial database migration.

  • Run Coalesce's code generation by either:

    • Running dotnet coalesce in the web project's root directory
    • Running the coalesce npm script (Vue) or gulp task (Knockout) in the Task Runner Explorer

You're now at a point where you can start creating your own pages!

',4),d=[i];function r(l,c,s,p,g,u){return t(),o("div",null,d)}const _=e(n,[["render",r]]);export{m as __pageData,_ as default}; diff --git a/assets/stacks_agnostic_getting-started-modeling.md.KbmYXIa-.lean.js b/assets/stacks_agnostic_getting-started-modeling.md.KbmYXIa-.lean.js new file mode 100644 index 000000000..91713e32a --- /dev/null +++ b/assets/stacks_agnostic_getting-started-modeling.md.KbmYXIa-.lean.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as o,R as a}from"./chunks/framework.g9eZ-ZSs.js";const m=JSON.parse('{"title":"Data Modeling","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/agnostic/getting-started-modeling.md","filePath":"stacks/agnostic/getting-started-modeling.md"}'),n={name:"stacks/agnostic/getting-started-modeling.md"},i=a("",4),d=[i];function r(l,c,s,p,g,u){return t(),o("div",null,d)}const _=e(n,[["render",r]]);export{m as __pageData,_ as default}; diff --git a/assets/stacks_disambiguation_external-view-model.md.m3dMwNWm.js b/assets/stacks_disambiguation_external-view-model.md.m3dMwNWm.js new file mode 100644 index 000000000..bb5367066 --- /dev/null +++ b/assets/stacks_disambiguation_external-view-model.md.m3dMwNWm.js @@ -0,0 +1 @@ +import{_ as e,o as a,c as t,R as o}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"TypeScript External ViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/disambiguation/external-view-model.md","filePath":"stacks/disambiguation/external-view-model.md"}'),s={name:"stacks/disambiguation/external-view-model.md"},r=o('

TypeScript External ViewModels

This is a disambiguation page for a concept in Coalesce that is implemented differently between the available front-end stack choices. Please select your preferred stack.

Vue

The Vue stack for Coalesce does not create dedicated ViewModels for External Types. The interfaces in the Model Layer are used as the only representation of External Types on the client.

Knockout

See: TypeScript External ViewModels

',6),l=[r];function i(n,c,d,p,h,m){return a(),t("div",null,l)}const f=e(s,[["render",i]]);export{_ as __pageData,f as default}; diff --git a/assets/stacks_disambiguation_external-view-model.md.m3dMwNWm.lean.js b/assets/stacks_disambiguation_external-view-model.md.m3dMwNWm.lean.js new file mode 100644 index 000000000..465306a81 --- /dev/null +++ b/assets/stacks_disambiguation_external-view-model.md.m3dMwNWm.lean.js @@ -0,0 +1 @@ +import{_ as e,o as a,c as t,R as o}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"TypeScript External ViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/disambiguation/external-view-model.md","filePath":"stacks/disambiguation/external-view-model.md"}'),s={name:"stacks/disambiguation/external-view-model.md"},r=o("",6),l=[r];function i(n,c,d,p,h,m){return a(),t("div",null,l)}const f=e(s,[["render",i]]);export{_ as __pageData,f as default}; diff --git a/assets/stacks_disambiguation_list-view-model.md.yRpM_Wq2.js b/assets/stacks_disambiguation_list-view-model.md.yRpM_Wq2.js new file mode 100644 index 000000000..e0d3c58ab --- /dev/null +++ b/assets/stacks_disambiguation_list-view-model.md.yRpM_Wq2.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,R as i}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"TypeScript List ViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/disambiguation/list-view-model.md","filePath":"stacks/disambiguation/list-view-model.md"}'),s={name:"stacks/disambiguation/list-view-model.md"},o=i('

TypeScript List ViewModels

This is a disambiguation page for a concept in Coalesce that is implemented differently between the available front-end stack choices. Please select your preferred stack.

Vue

See: Vue ListViewModels

Knockout

See: Knockout ListViewModels

',6),l=[o];function c(r,n,d,p,h,m){return t(),a("div",null,l)}const k=e(s,[["render",c]]);export{_ as __pageData,k as default}; diff --git a/assets/stacks_disambiguation_list-view-model.md.yRpM_Wq2.lean.js b/assets/stacks_disambiguation_list-view-model.md.yRpM_Wq2.lean.js new file mode 100644 index 000000000..65be2a627 --- /dev/null +++ b/assets/stacks_disambiguation_list-view-model.md.yRpM_Wq2.lean.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,R as i}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"TypeScript List ViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/disambiguation/list-view-model.md","filePath":"stacks/disambiguation/list-view-model.md"}'),s={name:"stacks/disambiguation/list-view-model.md"},o=i("",6),l=[o];function c(r,n,d,p,h,m){return t(),a("div",null,l)}const k=e(s,[["render",c]]);export{_ as __pageData,k as default}; diff --git a/assets/stacks_disambiguation_view-model.md.CJJYNLOF.js b/assets/stacks_disambiguation_view-model.md.CJJYNLOF.js new file mode 100644 index 000000000..ff45eaeda --- /dev/null +++ b/assets/stacks_disambiguation_view-model.md.CJJYNLOF.js @@ -0,0 +1 @@ +import{_ as e,o as a,c as t,R as o}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"TypeScript ViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/disambiguation/view-model.md","filePath":"stacks/disambiguation/view-model.md"}'),i={name:"stacks/disambiguation/view-model.md"},s=o('

TypeScript ViewModels

This is a disambiguation page for a concept in Coalesce that is implemented differently between the available front-end stack choices. Please select your preferred stack.

Vue

See: Vue ViewModels

Knockout

See: Knockout ViewModels

',6),c=[s];function r(l,n,d,p,h,m){return a(),t("div",null,c)}const k=e(i,[["render",r]]);export{_ as __pageData,k as default}; diff --git a/assets/stacks_disambiguation_view-model.md.CJJYNLOF.lean.js b/assets/stacks_disambiguation_view-model.md.CJJYNLOF.lean.js new file mode 100644 index 000000000..ab1cfffe8 --- /dev/null +++ b/assets/stacks_disambiguation_view-model.md.CJJYNLOF.lean.js @@ -0,0 +1 @@ +import{_ as e,o as a,c as t,R as o}from"./chunks/framework.g9eZ-ZSs.js";const _=JSON.parse('{"title":"TypeScript ViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/disambiguation/view-model.md","filePath":"stacks/disambiguation/view-model.md"}'),i={name:"stacks/disambiguation/view-model.md"},s=o("",6),c=[s];function r(l,n,d,p,h,m){return a(),t("div",null,c)}const k=e(i,[["render",r]]);export{_ as __pageData,k as default}; diff --git a/assets/stacks_ko_client_bindings.md.uLy2BLbT.js b/assets/stacks_ko_client_bindings.md.uLy2BLbT.js new file mode 100644 index 000000000..b36af756a --- /dev/null +++ b/assets/stacks_ko_client_bindings.md.uLy2BLbT.js @@ -0,0 +1,40 @@ +import{_ as a,D as n,o as p,c as i,I as o,R as l,k as e,a as t}from"./chunks/framework.g9eZ-ZSs.js";const me=JSON.parse('{"title":"Knockout Bindings","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/bindings.md","filePath":"stacks/ko/client/bindings.md"}'),c={name:"stacks/ko/client/bindings.md"},r=l(`

Knockout Bindings

Coalesce provides a number of knockout bindings that make common model binding activities much easier.

Editors Note: On this page, some bindings are split into their requisite HTML component with their data-bind component listed immediately after. Keep this in mind when reading.

Input Bindings

select2Ajax

html
<select data-bind="
+    select2Ajax: personId, 
+    url: '/api/Person/list', 
+    idField: 'personId', 
+    textField: 'Name', 
+    object: person, 
+    allowClear: true
+"></select>

Creates a select2 dropdown using the specified url and fields that can be used to select an object from the endpoint specified. Additional complimentary bindings include:

`,7),d=e("p",null,"The Coalesce List API url to call to populate the contents of the dropdown.",-1),h=e("p",null,[t("The name of the field on each item in the results of the AJAX call which contains the ID of the option. The value of this field will be set on the observable specified for the main "),e("code",null,"select2Ajax"),t(" binding.")],-1),u=e("p",null,"The name of the field on each item in the results of the AJAX call which contains the text to be displayed for each option.",-1),y=e("p",null,[t("An observable that holds the full object corresponding to the foreign key property being bound to. If the selected value changes, this will be set to null to avoid representation of incorrect data (unless "),e("code",null,"setObject"),t(" is used - see below).")],-1),m=l("

If true, the observable specified by the object binding will be set to the selected data when an option is chosen in the dropdown. Binding itemViewModel is required if this binding is set.

Additionally, requests to the API to populate the dropdown will request the entire object, as opposed to only the two fields specified for idField and textField like is normally done when this binding is missing or set to false. To override this behavior and continue requesting only specific fields even when setObject is true, add fields=field1,field2,... to the query string of the url binding.

",2),b=e("p",null,[t("A reference to the class that represents the type of the object held in the "),e("code",null,"object"),t(" observable. This is used when constructing new objects from the results of the API call. Not used if "),e("code",null,"setObject"),t(" is false or unspecified. For example, "),e("code",null,"setObject: true, itemViewModel: ViewModels.Person"),t(".")],-1),f=e("p",null,"The number of items to request in each call to the server.",-1),g=e("p",null,[t("A string containing the substring "),e("code",null,"{0}"),t(", which will be replaced with the text value of an option in the dropdown list when the option is displayed.")],-1),D=e("p",null,[t("A string containing the substring "),e("code",null,"{0}"),t(", which will be replaced with the text value of the selected option of the dropdown list.")],-1),C=e("p",null,"If true, a cache-busting querystring parameter will be included in AJAX requests.",-1),_=e("p",null,[t("Directly maps to select2 option "),e("code",null,"selectOnClose"),t(".")],-1),w=e("p",null,[t("Whether or not to allow the current select to be set to null. Directly maps to select2 option "),e("code",null,"allowClear"),t(".")],-1),v=e("p",null,[t("Placeholder when nothing is selected. Directly maps to select2 option "),e("code",null,"placeholder"),t(".")],-1),k=l(`

If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.

select2AjaxMultiple

html
<select multiple="multiple" data-bind="
+    select2AjaxMultiple: people, 
+    url: '/api/Person/list', 
+    idField: 'personId', 
+    textField: 'Name', 
+    itemViewModel: ViewModels.PersonCase
+"></select>

Creates a select2 multi-select input for choosing objects that participate as the foreign object in a many-to-many relationship with the current object. The primary select2AjaxMultiple binding takes the collection of items that make up the foreign side of the relationship. This is NOT the collection of the join objects (a.k.a. middle table objects) in the relationship.

Additional complimentary bindings include:

`,5),x=e("p",null,[t("The Coalesce List API url to call to populate the contents of the dropdown. In order to only receive specific fields from the server, add "),e("code",null,"fields=field1,field2,..."),t(" to the query string of the url, ensuring that at least the "),e("code",null,"idField"),t(" and "),e("code",null,"textField"),t(" are included in that collection.")],-1),q=e("p",null,"The name of the field on each item in the results of the AJAX call which contains the ID of the option. The value of this field will be set as the key of the foreign object in the many-to-many relationship.",-1),T=e("p",null,"The name of the field on each item in the results of the AJAX call which contains the text to be displayed for each option.",-1),A=e("p",null,[t("A reference to the class that represents the types in the supplied collection. For example, a many-to-many between "),e("code",null,"Person"),t(" and "),e("code",null,"Case"),t(" objects where "),e("code",null,"Case"),t(" is the object being bound to and "),e("code",null,"Person"),t(" is the type represented by a child collection, the correct value is "),e("code",null,"ViewModels.Person"),t(". This is used when constructing new objects representing the relationship when a new item is selected.")],-1),j=e("p",null,"The number of items to request in each call to the server.",-1),E=e("p",null,[t("A string containing the substring "),e("code",null,"{0}"),t(", which will be replaced with the text value of an option in the dropdown list when the option is displayed.")],-1),F=e("p",null,[t("A string containing the substring "),e("code",null,"{0}"),t(", which will be replaced with the text value of the selected option of the dropdown list.")],-1),I=e("p",null,"If true, a cache-busting querystring parameter will be included in AJAX requests.",-1),P=e("p",null,[t("Directly maps to select2 option "),e("code",null,"selectOnClose"),t(".")],-1),S=e("p",null,[t("Whether or not to allow the current select to be set to null. Directly maps to select2 option "),e("code",null,"allowClear"),t(".")],-1),M=e("p",null,[t("Placeholder when nothing is selected. Directly maps to select2 option "),e("code",null,"placeholder"),t(".")],-1),V=l(`

If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.

select2AjaxText

html
<select data-bind="
+    select2AjaxText: schoolName, 
+    url: '/api/Person/SchoolNames'
+"></select>

Creates a select2 dropdown against the specified url where the url returns a collection of string values that are potential selection candidates. The dropdown also allows the user to input any value they choose - the API simply serves suggested values.

`,4),O=l("

The url to call to populate the contents of the dropdown. This should be an endpoint that returns one of the following:

  • A raw string[]
  • An object that conforms to { list: string[] }
  • An object that conforms to { object: string[] }
  • An object that conforms to { list: { [prop: string]: string } } where the value given to resultField is a valid property of the returned objects.
  • An object that conforms to { object: { [prop: string]: string } } where the value given to resultField is a valid property of the returned objects.

The url will also be passed a search parameter and a page parameter appended to the query string. The chosen endpoint is responsible for implementing this functionality. Page size is expected to be some fixed value. Implementer should anticipate that the requested page may be out of range.

The cases listed above that accept arrays of objects (as opposed to arrays of strings) require that the resultField binding is also used. These are designed for obtaining string values from objects obtained from the standard list endpoint.

",4),B=e("p",null,[t("If provided, specifies a field on the objects returned from the API to pull the string values from. See examples in "),e("code",null,"url"),t(" above.")],-1),N=e("p",null,[t("If "),e("code",null,"false"),t(", the user's search input will not be presented as a valid selectable value; only the exact values obtained from the API endpoint will be selectable.")],-1),K=e("p",null,[t("Directly maps to select2 option "),e("code",null,"selectOnClose"),t(".")],-1),Y=e("p",null,[t("Whether or not to allow the current select to be set to null. Directly maps to select2 option "),e("code",null,"allowClear"),t(".")],-1),z=e("p",null,[t("Placeholder when nothing is selected. Directly maps to select2 option "),e("code",null,"placeholder"),t(".")],-1),R=l(`

If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.

select2

html
<select data-bind="select2: selectedNumber">
+    <option value="1">Option 1</option>
+    <option value="2">Option 2</option>
+</select>

Sets up a basic select2 dropdown on an HTML select element. Dropdown contents should be populated through other means - either using stock Knockout bindings or server-side static contents (via cshtml).

`,4),$=e("p",null,[t("Directly maps to select2 option "),e("code",null,"selectOnClose"),t(".")],-1),W=e("p",null,[t("Whether or not to allow the current select to be set to null. Directly maps to select2 option "),e("code",null,"allowClear"),t(".")],-1),J=e("p",null,[t("Placeholder when nothing is selected. Directly maps to select2 option "),e("code",null,"placeholder"),t(".")],-1),L=l(`

If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.

datePicker

html
<div class="input-group date">
+    <input data-bind="datePicker: birthDate" type="text" class="form-control" id-prefix="datePicker" />
+    <span class="input-group-addon">
+        <span class="fa fa-calendar"></span>
+    </span>
+</div>

Creates a date/time picker for changing a moment.Moment property. The control used is bootstrap-datetimepicker

`,4),X=e("p",null,[t("If true, the date portion of the "),e("code",null,"moment.Moment"),t(" object will be preserved by the date picker. Only the time portion will be changed by user input.")],-1),H=e("p",null,[t("If true, the time portion of the "),e("code",null,"moment.Moment"),t(" object will be preserved by the date picker. Only the date portion will be changed by user input.")],-1),Q=e("p",null,[t("Specify the moment-compatible format string to be used as the display format for the text value shown on the date picker. Defaults to "),e("code",null,"M/D/YY h:mm a"),t(". Direct pass-through to "),e("a",{href:"https://www.npmjs.com/package/eonasdan-bootstrap-datetimepicker",target:"_blank",rel:"noreferrer"},"bootstrap-datetimepicker"),t(".")],-1),U=e("p",null,[t("If true, places the time picker next to the date picker, visible at the same time. Direct pass-through to corresponding "),e("a",{href:"https://www.npmjs.com/package/eonasdan-bootstrap-datetimepicker",target:"_blank",rel:"noreferrer"},"bootstrap-datetimepicker"),t(" option.")],-1),Z=e("p",null,[t("Direct pass-through to corresponding "),e("a",{href:"https://www.npmjs.com/package/eonasdan-bootstrap-datetimepicker",target:"_blank",rel:"noreferrer"},"bootstrap-datetimepicker"),t(" option.")],-1),G=e("p",null,[t("Direct pass-through to corresponding "),e("a",{href:"https://www.npmjs.com/package/eonasdan-bootstrap-datetimepicker",target:"_blank",rel:"noreferrer"},"bootstrap-datetimepicker"),t(" option.")],-1),ee=e("p",null,[t("Override key bindings of the date picker. Direct pass-through to corresponding "),e("a",{href:"https://www.npmjs.com/package/eonasdan-bootstrap-datetimepicker",target:"_blank",rel:"noreferrer"},"bootstrap-datetimepicker"),t(" option.")],-1),te=l(`

If true, the datePicker will update the underlying observable on each input change. Otherwise, the observable will only be changed when the datePicker loses focus (on blur).

saveImmediately

html
<div data-bind="with: product">
+    <input type="text" data-bind="textValue: description, saveImmediately: true" />
+</div>

When used in a context where $data is a Coalesce.BaseViewModel, that object's saveTimeoutMs configuration property (see ViewModel Configuration) will be set to 0 when the element it is placed on gains focus. This value will be reverted to its previous value when the element loses focus. This will cause any changes to the object, including any observable bound as input on the element, to trigger a save immediately rather than after a delay (defaults to 500ms).

delaySave

html
<div data-bind="with: product">
+    <input type="text" data-bind="textValue: description, delaySave: true" />
+</div>

When used in a context where $data is a Coalesce.BaseViewModel, that object's autoSaveEnabled configuration property (see ViewModel Configuration) will be set to false when the element it is placed on gains focus. This will cause any changes to the object, including any observable bound as input on the element, to not trigger auto saves while the element has focus. When the element loses focus, the autoSaveEnabled flag will be reverted to its previous value and an attempt will be made to save the object.

Display Bindings

tooltip

html
<div data-bind="tooltip: tooltipText">Some Element</div>
+<div data-bind="tooltip: {title: note, placement: 'bottom', animation: false}">Some Element</div>

Wrapper around the Bootstrap tooltip component. Binding can either be simply a string (or observable string), or it can be an object that will be passed directly to the Bootstrap tooltip component.

fadeVisible

html
<div data-bind="fadeVisible: isVisible">Some Element</div>

Similar to the Knockout visible binding, but uses jQuery fadeIn/fadeOut calls to perform the transition.

slideVisible

html
<div data-bind="slideVisible: isVisible">Some Element</div>

Similar to the Knockout visible, but uses jQuery slideIn/slideOut calls to perform the transition.

moment

html
<div data-bind="moment: momentObservable"></div>
+<div data-bind="moment: momentObservable, format: 'MM/DD/YYYY hh:mm a'"></div>

Controls the text of the element by calling the format method on a moment object.

momentFromNow

html
<div data-bind="momentFromNow: momentObservable"></div>
+<div data-bind="momentFromNow: momentObservable, shorten: true"></div>

Controls the text of the element by calling the fromNow method on a moment object. If shorten is true, certain phrases will be slightly shortened.

Utility Bindings

let

html
<div class="item">
+    <!-- ko let: { showControls: $data.isEditing() || $parent.editingChildren() } -->
+    <button data-bind="click: $root.editItem, visible: showControls">Edit</button>
+    <span data-bind="text: name"></span>
+    <button data-bind="click: $root.deleteItem, visible: showControls">Delete</button>
+    <!-- /ko -->
+</div>

The let binding is a somewhat common construct used in Knockout applications, but isn't part of Knockout itself. It effectively allows the creation of variables in the binding context, allowing complex statements which may be used multiple times to be aliased for both clarity of code and better performance.

Knockout Binding Defaults

Knockout Helpers

These are static properties on IntelliTect.Coalesce.Knockout.Helpers.Knockout you can assign to somewhere in the app lifecycle startup to change the default markup generated server-side when using @Knockout.* methods to render Knockout bindings in your .cshtml files.

`,30),se=e("p",null,"The default number of Bootstrap grid columns a field label should span across.",-1),oe=e("p",null,"The default number of Bootstrap grid columns a form input should span across.",-1),le=e("p",null,[t("Sets the default date-only format to be used by all date/time pickers. This only applies to models with a date-only "),e("a",{href:"/Coalesce/modeling/model-components/attributes/date-type.html"},"[DateType]"),t(" attribute.")],-1),ae=e("p",null,[t("Sets the default time-only format to be used by all date/time pickers. This only applies to models with a time-only "),e("a",{href:"/Coalesce/modeling/model-components/attributes/date-type.html"},"[DateType]"),t(" attribute.")],-1),ne=l(`

Sets the default date/time format to be used by all date/time pickers. This only applies to DateTimeOffset model properties that do not have a limiting [DateType] attribute.

Note

DefaultDateFormat, DefaultTimeFormat and DefaultDateTimeFormat all take various formatting strings from the Moment.js library. A full listing can be found on the Moment website.

Timezone

The date/time picker properties can be coupled with DateTimeOffset model properties to display time values localized for the current user's locale. If you want to make the localization static, simply include a script block in your _Layout.cshtml or in a specific view that sets the default for Moment.js:

html
<script>
+moment.tz.setDefault("America/Chicago");
+</script>

Note

This needs to happen after Moment is loaded, but before the bootstrap-datetimepicker script is loaded.

`,6);function pe(ie,ce,re,de,he,ue){const s=n("Prop");return p(),i("div",null,[r,o(s,{def:"url: string",lang:"ts","id-prefix":"select2Ajax"}),d,o(s,{def:"idField: string",lang:"ts","id-prefix":"select2Ajax"}),h,o(s,{def:"textField: string",lang:"ts","id-prefix":"select2Ajax"}),u,o(s,{def:"object?: KnockoutObservable",lang:"ts","id-prefix":"select2Ajax"}),y,o(s,{def:"setObject: boolean = false",lang:"ts","id-prefix":"select2Ajax"}),m,o(s,{def:"itemViewModel?: (new (newItem: object) => Coalesce.BaseViewModel)",lang:"ts","id-prefix":"select2Ajax"}),b,o(s,{def:"pageSize: number = 25",lang:"ts","id-prefix":"select2Ajax"}),f,o(s,{def:"format: string = '{0}'",lang:"ts","id-prefix":"select2Ajax"}),g,o(s,{def:"selectionFormat: string = '{0}'",lang:"ts","id-prefix":"select2Ajax"}),D,o(s,{def:"cache: boolean = true",lang:"ts","id-prefix":"select2Ajax"}),C,o(s,{def:"selectOnClose: boolean = false",lang:"ts","id-prefix":"select2Ajax"}),_,o(s,{def:"allowClear: boolean = true",lang:"ts","id-prefix":"select2Ajax"}),w,o(s,{def:"placeholder: string = 'select'",lang:"ts","id-prefix":"select2Ajax"}),v,o(s,{def:"openOnFocus: boolean = false",lang:"ts","id-prefix":"select2Ajax"}),k,o(s,{def:"url: string",lang:"ts","id-prefix":"select2AjaxMultiple"}),x,o(s,{def:"idField: string",lang:"ts","id-prefix":"select2AjaxMultiple"}),q,o(s,{def:"textField: string",lang:"ts","id-prefix":"select2AjaxMultiple"}),T,o(s,{def:"itemViewModel: (new (newItem: object) => Coalesce.BaseViewModel)",lang:"ts","id-prefix":"select2AjaxMultiple"}),A,o(s,{def:"pageSize: number = 25",lang:"ts","id-prefix":"select2AjaxMultiple"}),j,o(s,{def:"format: string = '{0}'",lang:"ts","id-prefix":"select2AjaxMultiple"}),E,o(s,{def:"selectionFormat: string = '{0}'",lang:"ts","id-prefix":"select2AjaxMultiple"}),F,o(s,{def:"cache: boolean = true",lang:"ts","id-prefix":"select2AjaxMultiple"}),I,o(s,{def:"selectOnClose: boolean = false",lang:"ts","id-prefix":"select2AjaxMultiple"}),P,o(s,{def:"allowClear: boolean = true",lang:"ts","id-prefix":"select2AjaxMultiple"}),S,o(s,{def:"placeholder: string = 'select'",lang:"ts","id-prefix":"select2AjaxMultiple"}),M,o(s,{def:"openOnFocus: boolean = false",lang:"ts","id-prefix":"select2AjaxMultiple"}),V,o(s,{def:"url: string",lang:"ts","id-prefix":"select2AjaxText"}),O,o(s,{def:"resultField?: string",lang:"ts","id-prefix":"select2AjaxText"}),B,o(s,{def:"allowCustom: boolean = true",lang:"ts","id-prefix":"select2AjaxText"}),N,o(s,{def:"selectOnClose: boolean = false",lang:"ts","id-prefix":"select2AjaxText"}),K,o(s,{def:"allowClear: boolean = true",lang:"ts","id-prefix":"select2AjaxText"}),Y,o(s,{def:"placeholder: string = 'select'",lang:"ts","id-prefix":"select2AjaxText"}),z,o(s,{def:"openOnFocus: boolean = false",lang:"ts","id-prefix":"select2AjaxText"}),R,o(s,{def:"selectOnClose: boolean = false",lang:"ts","id-prefix":"select2"}),$,o(s,{def:"allowClear: boolean = true",lang:"ts","id-prefix":"select2"}),W,o(s,{def:"placeholder: string = 'select'",lang:"ts","id-prefix":"select2"}),J,o(s,{def:"openOnFocus: boolean = false",lang:"ts","id-prefix":"select2"}),L,o(s,{def:"preserveDate: boolean = false",lang:"ts","id-prefix":"date-picker"}),X,o(s,{def:"preserveTime: boolean = false",lang:"ts","id-prefix":"date-picker"}),H,o(s,{def:"format: string = 'M/D/YY h:mm a'",lang:"ts","id-prefix":"date-picker"}),Q,o(s,{def:"sideBySide: boolean = false",lang:"ts","id-prefix":"date-picker"}),U,o(s,{def:"stepping: number = 1",lang:"ts","id-prefix":"date-picker"}),Z,o(s,{def:"timeZone: string = ''",lang:"ts","id-prefix":"date-picker"}),G,o(s,{def:"keyBinds = { left: null, right: null, delete: null }",lang:"ts","id-prefix":"date-picker"}),ee,o(s,{def:"updateImmediate: boolean = false",lang:"ts","id-prefix":"date-picker"}),te,o(s,{def:"public static int DefaultLabelCols { get; set; } = 3;"}),se,o(s,{def:"public static int DefaultInputCols { get; set; } = 9;"}),oe,o(s,{def:'public static string DefaultDateFormat { get; set; } = "M/D/YYYY";'}),le,o(s,{def:'public static string DefaultTimeFormat { get; set; } = "h:mm a";'}),ae,o(s,{def:'public static string DefaultDateTimeFormat { get; set; } = "M/D/YYYY h:mm a";'}),ne])}const be=a(c,[["render",pe]]);export{me as __pageData,be as default}; diff --git a/assets/stacks_ko_client_bindings.md.uLy2BLbT.lean.js b/assets/stacks_ko_client_bindings.md.uLy2BLbT.lean.js new file mode 100644 index 000000000..ec48c21de --- /dev/null +++ b/assets/stacks_ko_client_bindings.md.uLy2BLbT.lean.js @@ -0,0 +1 @@ +import{_ as a,D as n,o as p,c as i,I as o,R as l,k as e,a as t}from"./chunks/framework.g9eZ-ZSs.js";const me=JSON.parse('{"title":"Knockout Bindings","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/bindings.md","filePath":"stacks/ko/client/bindings.md"}'),c={name:"stacks/ko/client/bindings.md"},r=l("",7),d=e("p",null,"The Coalesce List API url to call to populate the contents of the dropdown.",-1),h=e("p",null,[t("The name of the field on each item in the results of the AJAX call which contains the ID of the option. The value of this field will be set on the observable specified for the main "),e("code",null,"select2Ajax"),t(" binding.")],-1),u=e("p",null,"The name of the field on each item in the results of the AJAX call which contains the text to be displayed for each option.",-1),y=e("p",null,[t("An observable that holds the full object corresponding to the foreign key property being bound to. If the selected value changes, this will be set to null to avoid representation of incorrect data (unless "),e("code",null,"setObject"),t(" is used - see below).")],-1),m=l("",2),b=e("p",null,[t("A reference to the class that represents the type of the object held in the "),e("code",null,"object"),t(" observable. This is used when constructing new objects from the results of the API call. Not used if "),e("code",null,"setObject"),t(" is false or unspecified. For example, "),e("code",null,"setObject: true, itemViewModel: ViewModels.Person"),t(".")],-1),f=e("p",null,"The number of items to request in each call to the server.",-1),g=e("p",null,[t("A string containing the substring "),e("code",null,"{0}"),t(", which will be replaced with the text value of an option in the dropdown list when the option is displayed.")],-1),D=e("p",null,[t("A string containing the substring "),e("code",null,"{0}"),t(", which will be replaced with the text value of the selected option of the dropdown list.")],-1),C=e("p",null,"If true, a cache-busting querystring parameter will be included in AJAX requests.",-1),_=e("p",null,[t("Directly maps to select2 option "),e("code",null,"selectOnClose"),t(".")],-1),w=e("p",null,[t("Whether or not to allow the current select to be set to null. Directly maps to select2 option "),e("code",null,"allowClear"),t(".")],-1),v=e("p",null,[t("Placeholder when nothing is selected. Directly maps to select2 option "),e("code",null,"placeholder"),t(".")],-1),k=l("",5),x=e("p",null,[t("The Coalesce List API url to call to populate the contents of the dropdown. In order to only receive specific fields from the server, add "),e("code",null,"fields=field1,field2,..."),t(" to the query string of the url, ensuring that at least the "),e("code",null,"idField"),t(" and "),e("code",null,"textField"),t(" are included in that collection.")],-1),q=e("p",null,"The name of the field on each item in the results of the AJAX call which contains the ID of the option. The value of this field will be set as the key of the foreign object in the many-to-many relationship.",-1),T=e("p",null,"The name of the field on each item in the results of the AJAX call which contains the text to be displayed for each option.",-1),A=e("p",null,[t("A reference to the class that represents the types in the supplied collection. For example, a many-to-many between "),e("code",null,"Person"),t(" and "),e("code",null,"Case"),t(" objects where "),e("code",null,"Case"),t(" is the object being bound to and "),e("code",null,"Person"),t(" is the type represented by a child collection, the correct value is "),e("code",null,"ViewModels.Person"),t(". This is used when constructing new objects representing the relationship when a new item is selected.")],-1),j=e("p",null,"The number of items to request in each call to the server.",-1),E=e("p",null,[t("A string containing the substring "),e("code",null,"{0}"),t(", which will be replaced with the text value of an option in the dropdown list when the option is displayed.")],-1),F=e("p",null,[t("A string containing the substring "),e("code",null,"{0}"),t(", which will be replaced with the text value of the selected option of the dropdown list.")],-1),I=e("p",null,"If true, a cache-busting querystring parameter will be included in AJAX requests.",-1),P=e("p",null,[t("Directly maps to select2 option "),e("code",null,"selectOnClose"),t(".")],-1),S=e("p",null,[t("Whether or not to allow the current select to be set to null. Directly maps to select2 option "),e("code",null,"allowClear"),t(".")],-1),M=e("p",null,[t("Placeholder when nothing is selected. Directly maps to select2 option "),e("code",null,"placeholder"),t(".")],-1),V=l("",4),O=l("",4),B=e("p",null,[t("If provided, specifies a field on the objects returned from the API to pull the string values from. See examples in "),e("code",null,"url"),t(" above.")],-1),N=e("p",null,[t("If "),e("code",null,"false"),t(", the user's search input will not be presented as a valid selectable value; only the exact values obtained from the API endpoint will be selectable.")],-1),K=e("p",null,[t("Directly maps to select2 option "),e("code",null,"selectOnClose"),t(".")],-1),Y=e("p",null,[t("Whether or not to allow the current select to be set to null. Directly maps to select2 option "),e("code",null,"allowClear"),t(".")],-1),z=e("p",null,[t("Placeholder when nothing is selected. Directly maps to select2 option "),e("code",null,"placeholder"),t(".")],-1),R=l("",4),$=e("p",null,[t("Directly maps to select2 option "),e("code",null,"selectOnClose"),t(".")],-1),W=e("p",null,[t("Whether or not to allow the current select to be set to null. Directly maps to select2 option "),e("code",null,"allowClear"),t(".")],-1),J=e("p",null,[t("Placeholder when nothing is selected. Directly maps to select2 option "),e("code",null,"placeholder"),t(".")],-1),L=l("",4),X=e("p",null,[t("If true, the date portion of the "),e("code",null,"moment.Moment"),t(" object will be preserved by the date picker. Only the time portion will be changed by user input.")],-1),H=e("p",null,[t("If true, the time portion of the "),e("code",null,"moment.Moment"),t(" object will be preserved by the date picker. Only the date portion will be changed by user input.")],-1),Q=e("p",null,[t("Specify the moment-compatible format string to be used as the display format for the text value shown on the date picker. Defaults to "),e("code",null,"M/D/YY h:mm a"),t(". Direct pass-through to "),e("a",{href:"https://www.npmjs.com/package/eonasdan-bootstrap-datetimepicker",target:"_blank",rel:"noreferrer"},"bootstrap-datetimepicker"),t(".")],-1),U=e("p",null,[t("If true, places the time picker next to the date picker, visible at the same time. Direct pass-through to corresponding "),e("a",{href:"https://www.npmjs.com/package/eonasdan-bootstrap-datetimepicker",target:"_blank",rel:"noreferrer"},"bootstrap-datetimepicker"),t(" option.")],-1),Z=e("p",null,[t("Direct pass-through to corresponding "),e("a",{href:"https://www.npmjs.com/package/eonasdan-bootstrap-datetimepicker",target:"_blank",rel:"noreferrer"},"bootstrap-datetimepicker"),t(" option.")],-1),G=e("p",null,[t("Direct pass-through to corresponding "),e("a",{href:"https://www.npmjs.com/package/eonasdan-bootstrap-datetimepicker",target:"_blank",rel:"noreferrer"},"bootstrap-datetimepicker"),t(" option.")],-1),ee=e("p",null,[t("Override key bindings of the date picker. Direct pass-through to corresponding "),e("a",{href:"https://www.npmjs.com/package/eonasdan-bootstrap-datetimepicker",target:"_blank",rel:"noreferrer"},"bootstrap-datetimepicker"),t(" option.")],-1),te=l("",30),se=e("p",null,"The default number of Bootstrap grid columns a field label should span across.",-1),oe=e("p",null,"The default number of Bootstrap grid columns a form input should span across.",-1),le=e("p",null,[t("Sets the default date-only format to be used by all date/time pickers. This only applies to models with a date-only "),e("a",{href:"/Coalesce/modeling/model-components/attributes/date-type.html"},"[DateType]"),t(" attribute.")],-1),ae=e("p",null,[t("Sets the default time-only format to be used by all date/time pickers. This only applies to models with a time-only "),e("a",{href:"/Coalesce/modeling/model-components/attributes/date-type.html"},"[DateType]"),t(" attribute.")],-1),ne=l("",6);function pe(ie,ce,re,de,he,ue){const s=n("Prop");return p(),i("div",null,[r,o(s,{def:"url: string",lang:"ts","id-prefix":"select2Ajax"}),d,o(s,{def:"idField: string",lang:"ts","id-prefix":"select2Ajax"}),h,o(s,{def:"textField: string",lang:"ts","id-prefix":"select2Ajax"}),u,o(s,{def:"object?: KnockoutObservable",lang:"ts","id-prefix":"select2Ajax"}),y,o(s,{def:"setObject: boolean = false",lang:"ts","id-prefix":"select2Ajax"}),m,o(s,{def:"itemViewModel?: (new (newItem: object) => Coalesce.BaseViewModel)",lang:"ts","id-prefix":"select2Ajax"}),b,o(s,{def:"pageSize: number = 25",lang:"ts","id-prefix":"select2Ajax"}),f,o(s,{def:"format: string = '{0}'",lang:"ts","id-prefix":"select2Ajax"}),g,o(s,{def:"selectionFormat: string = '{0}'",lang:"ts","id-prefix":"select2Ajax"}),D,o(s,{def:"cache: boolean = true",lang:"ts","id-prefix":"select2Ajax"}),C,o(s,{def:"selectOnClose: boolean = false",lang:"ts","id-prefix":"select2Ajax"}),_,o(s,{def:"allowClear: boolean = true",lang:"ts","id-prefix":"select2Ajax"}),w,o(s,{def:"placeholder: string = 'select'",lang:"ts","id-prefix":"select2Ajax"}),v,o(s,{def:"openOnFocus: boolean = false",lang:"ts","id-prefix":"select2Ajax"}),k,o(s,{def:"url: string",lang:"ts","id-prefix":"select2AjaxMultiple"}),x,o(s,{def:"idField: string",lang:"ts","id-prefix":"select2AjaxMultiple"}),q,o(s,{def:"textField: string",lang:"ts","id-prefix":"select2AjaxMultiple"}),T,o(s,{def:"itemViewModel: (new (newItem: object) => Coalesce.BaseViewModel)",lang:"ts","id-prefix":"select2AjaxMultiple"}),A,o(s,{def:"pageSize: number = 25",lang:"ts","id-prefix":"select2AjaxMultiple"}),j,o(s,{def:"format: string = '{0}'",lang:"ts","id-prefix":"select2AjaxMultiple"}),E,o(s,{def:"selectionFormat: string = '{0}'",lang:"ts","id-prefix":"select2AjaxMultiple"}),F,o(s,{def:"cache: boolean = true",lang:"ts","id-prefix":"select2AjaxMultiple"}),I,o(s,{def:"selectOnClose: boolean = false",lang:"ts","id-prefix":"select2AjaxMultiple"}),P,o(s,{def:"allowClear: boolean = true",lang:"ts","id-prefix":"select2AjaxMultiple"}),S,o(s,{def:"placeholder: string = 'select'",lang:"ts","id-prefix":"select2AjaxMultiple"}),M,o(s,{def:"openOnFocus: boolean = false",lang:"ts","id-prefix":"select2AjaxMultiple"}),V,o(s,{def:"url: string",lang:"ts","id-prefix":"select2AjaxText"}),O,o(s,{def:"resultField?: string",lang:"ts","id-prefix":"select2AjaxText"}),B,o(s,{def:"allowCustom: boolean = true",lang:"ts","id-prefix":"select2AjaxText"}),N,o(s,{def:"selectOnClose: boolean = false",lang:"ts","id-prefix":"select2AjaxText"}),K,o(s,{def:"allowClear: boolean = true",lang:"ts","id-prefix":"select2AjaxText"}),Y,o(s,{def:"placeholder: string = 'select'",lang:"ts","id-prefix":"select2AjaxText"}),z,o(s,{def:"openOnFocus: boolean = false",lang:"ts","id-prefix":"select2AjaxText"}),R,o(s,{def:"selectOnClose: boolean = false",lang:"ts","id-prefix":"select2"}),$,o(s,{def:"allowClear: boolean = true",lang:"ts","id-prefix":"select2"}),W,o(s,{def:"placeholder: string = 'select'",lang:"ts","id-prefix":"select2"}),J,o(s,{def:"openOnFocus: boolean = false",lang:"ts","id-prefix":"select2"}),L,o(s,{def:"preserveDate: boolean = false",lang:"ts","id-prefix":"date-picker"}),X,o(s,{def:"preserveTime: boolean = false",lang:"ts","id-prefix":"date-picker"}),H,o(s,{def:"format: string = 'M/D/YY h:mm a'",lang:"ts","id-prefix":"date-picker"}),Q,o(s,{def:"sideBySide: boolean = false",lang:"ts","id-prefix":"date-picker"}),U,o(s,{def:"stepping: number = 1",lang:"ts","id-prefix":"date-picker"}),Z,o(s,{def:"timeZone: string = ''",lang:"ts","id-prefix":"date-picker"}),G,o(s,{def:"keyBinds = { left: null, right: null, delete: null }",lang:"ts","id-prefix":"date-picker"}),ee,o(s,{def:"updateImmediate: boolean = false",lang:"ts","id-prefix":"date-picker"}),te,o(s,{def:"public static int DefaultLabelCols { get; set; } = 3;"}),se,o(s,{def:"public static int DefaultInputCols { get; set; } = 9;"}),oe,o(s,{def:'public static string DefaultDateFormat { get; set; } = "M/D/YYYY";'}),le,o(s,{def:'public static string DefaultTimeFormat { get; set; } = "h:mm a";'}),ae,o(s,{def:'public static string DefaultDateTimeFormat { get; set; } = "M/D/YYYY h:mm a";'}),ne])}const be=a(c,[["render",pe]]);export{me as __pageData,be as default}; diff --git a/assets/stacks_ko_client_external-view-model.md.mJX84X3y.js b/assets/stacks_ko_client_external-view-model.md.mJX84X3y.js new file mode 100644 index 000000000..b86123977 --- /dev/null +++ b/assets/stacks_ko_client_external-view-model.md.mJX84X3y.js @@ -0,0 +1,8 @@ +import{_ as a,D as l,o as n,c as s,I as r,R as i,k as e,a as o}from"./chunks/framework.g9eZ-ZSs.js";const T=JSON.parse('{"title":"TypeScript External ViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/external-view-model.md","filePath":"stacks/ko/client/external-view-model.md"}'),c={name:"stacks/ko/client/external-view-model.md"},p=i('

TypeScript External ViewModels

For all External Types in your model, Coalesce will generate a TypeScript class that provides a bare-bones representation of that type's properties.

These ViewModels are dependent on Knockout, and are designed to be used directly from Knockout bindings in your HTML. All data properties on the generated model are Knockout observables.

Base Members

The TypeScript ViewModels for external types do not have a common base class, and do not have any of the behaviors or convenience properties that the regular TypeScript ViewModels for database-mapped classes have.

Model-Specific Members

Data Properties

',7),d=e("p",null,[o("For each exposed property on the underlying EF POCO, a "),e("code",null,"KnockoutObservable"),o(" property will exist on the TypeScript model. For navigation properties, these will be typed with the corresponding TypeScript ViewModel for the other end of the relationship. For collections (including collection navigation properties), these properties will be "),e("code",null,"KnockoutObservableArray"),o(" objects.")],-1),b=e("h3",{id:"enum-members",tabindex:"-1"},[o("Enum Members "),e("a",{class:"header-anchor",href:"#enum-members","aria-label":'Permalink to "Enum Members"'},"​")],-1),m=e("p",null,[o("For each "),e("code",null,"enum"),o(" property on your POCO, the following will be created:")],-1),u=e("p",null,[o("A "),e("code",null,"KnockoutComputed"),o(" property that will provide the text to display for that property.")],-1);function h(k,_,v,y,f,x){const t=l("Prop");return n(),s("div",null,[p,r(t,{def:` +public personId: KnockoutObservable = ko.observable(null); +public fullName: KnockoutObservable = ko.observable(null); +public gender: KnockoutObservable = ko.observable(null); +public companyId: KnockoutObservable = ko.observable(null); +public company: KnockoutObservable = ko.observable(null); +public addresses: KnockoutObservableArray = ko.observableArray([]); +public birthDate: KnockoutObservable = ko.observable(moment());`,lang:"ts",id:"data-property-members"}),d,b,m,r(t,{def:"public genderText: KnockoutComputed",lang:"ts"}),u])}const g=a(c,[["render",h]]);export{T as __pageData,g as default}; diff --git a/assets/stacks_ko_client_external-view-model.md.mJX84X3y.lean.js b/assets/stacks_ko_client_external-view-model.md.mJX84X3y.lean.js new file mode 100644 index 000000000..2bf1b4162 --- /dev/null +++ b/assets/stacks_ko_client_external-view-model.md.mJX84X3y.lean.js @@ -0,0 +1,8 @@ +import{_ as a,D as l,o as n,c as s,I as r,R as i,k as e,a as o}from"./chunks/framework.g9eZ-ZSs.js";const T=JSON.parse('{"title":"TypeScript External ViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/external-view-model.md","filePath":"stacks/ko/client/external-view-model.md"}'),c={name:"stacks/ko/client/external-view-model.md"},p=i("",7),d=e("p",null,[o("For each exposed property on the underlying EF POCO, a "),e("code",null,"KnockoutObservable"),o(" property will exist on the TypeScript model. For navigation properties, these will be typed with the corresponding TypeScript ViewModel for the other end of the relationship. For collections (including collection navigation properties), these properties will be "),e("code",null,"KnockoutObservableArray"),o(" objects.")],-1),b=e("h3",{id:"enum-members",tabindex:"-1"},[o("Enum Members "),e("a",{class:"header-anchor",href:"#enum-members","aria-label":'Permalink to "Enum Members"'},"​")],-1),m=e("p",null,[o("For each "),e("code",null,"enum"),o(" property on your POCO, the following will be created:")],-1),u=e("p",null,[o("A "),e("code",null,"KnockoutComputed"),o(" property that will provide the text to display for that property.")],-1);function h(k,_,v,y,f,x){const t=l("Prop");return n(),s("div",null,[p,r(t,{def:` +public personId: KnockoutObservable = ko.observable(null); +public fullName: KnockoutObservable = ko.observable(null); +public gender: KnockoutObservable = ko.observable(null); +public companyId: KnockoutObservable = ko.observable(null); +public company: KnockoutObservable = ko.observable(null); +public addresses: KnockoutObservableArray = ko.observableArray([]); +public birthDate: KnockoutObservable = ko.observable(moment());`,lang:"ts",id:"data-property-members"}),d,b,m,r(t,{def:"public genderText: KnockoutComputed",lang:"ts"}),u])}const g=a(c,[["render",h]]);export{T as __pageData,g as default}; diff --git a/assets/stacks_ko_client_list-view-model.md.sYLWdQOg.js b/assets/stacks_ko_client_list-view-model.md.sYLWdQOg.js new file mode 100644 index 000000000..2701d3e18 --- /dev/null +++ b/assets/stacks_ko_client_list-view-model.md.sYLWdQOg.js @@ -0,0 +1,12 @@ +import{_ as n,D as l,o as i,c as r,I as o,R as a,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const Z=JSON.parse('{"title":"TypeScript ListViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/list-view-model.md","filePath":"stacks/ko/client/list-view-model.md"}'),c={name:"stacks/ko/client/list-view-model.md"},d=a('

TypeScript ListViewModels

In addition to TypeScript ViewModels for interacting with instances of your data classes in TypeScript, Coalesce will also generated a List ViewModel for loading searched, sorted, paginated data from the server.

These ListViewModels, like the ViewModels, are dependent on Knockout and are designed to be used directly from Knockout bindings in your HTML.

Base Members

The following members are defined on BaseListViewModel<> and are available to the ListViewModels for all of your model types:

',5),h=e("p",null,"Name of the primary key of the model that this list represents.",-1),p=e("p",null,[s("String that is used to control loading and serialization on the server. See "),e("a",{href:"/Coalesce/concepts/includes.html"},"Includes String"),s(" for more information.")],-1),m=e("p",null,"The collection of items that have been loaded from the server.",-1),u=e("p",null,"Adds a new item to the items collection.",-1),g=e("p",null,"Deletes an item and removes it from the items collection.",-1),f=e("p",null,"An arbitrary URL query string to append to the API call when loading the list of items.",-1),b=e("p",null,[s("Search criteria for the list. This can be easily bound to with a text box for easy search behavior. See "),e("a",{href:"/Coalesce/modeling/model-components/attributes/search.html"},"[Search]"),s(" for a detailed look at how searching works in Coalesce.")],-1),_=e("p",null,"True if the list is loading.",-1),y=e("p",null,"True once the list has been loaded.",-1),w=e("p",null,"Load the list using current parameters for paging, searching, etc Result is placed into the items property.",-1),C=e("p",null,"If a load failed, this is a message about why it failed.",-1),v=e("p",null,"Gets the count of items without getting all the items. Result is placed into the count property.",-1),D=e("p",null,"The result of getCount(), or the total on this page.",-1),k=e("p",null,"Total count of items, even ones that are not on the page.",-1),S=e("p",null,"Change to the next page.",-1),T=e("p",null,"True if there is another page after the current page.",-1),M=e("p",null,"Change to the previous page.",-1),P=e("p",null,"True if there is another page before the current page.",-1),V=e("p",null,"Page number. This can be set to get a new page.",-1),L=e("p",null,"Total page count",-1),x=e("p",null,"Number of items on a page.",-1),N=e("p",null,"Name of a field by which this list will be loaded in ascending order.",-1),O=e("p",null,[s("If set to "),e("code",null,'"none"'),s(", default sorting behavior, including behavior defined with use of "),e("a",{href:"/Coalesce/modeling/model-components/attributes/default-order-by.html"},"[DefaultOrderBy]"),s(" in C# POCOs, is suppressed.")],-1),I=e("p",null,"Name of a field by which this list will be loaded in descending order.",-1),A=e("p",null,"Toggles sorting between ascending, descending, and no order on the specified field.",-1),E=e("h2",{id:"model-specific-members",tabindex:"-1"},[s("Model-Specific Members "),e("a",{class:"header-anchor",href:"#model-specific-members","aria-label":'Permalink to "Model-Specific Members"'},"​")],-1),K=e("h3",{id:"configuration",tabindex:"-1"},[s("Configuration "),e("a",{class:"header-anchor",href:"#configuration","aria-label":'Permalink to "Configuration"'},"​")],-1),F=e("p",null,[s("A static configuration object for configuring all instances of the ListViewModel's type is created. See "),e("a",{href:"/Coalesce/stacks/ko/client/model-config.html"},"ViewModel Configuration"),s(".")],-1),q=e("p",null,[s("An per-instance configuration object for configuring each specific ListViewModel instance is created. See "),e("a",{href:"/Coalesce/stacks/ko/client/model-config.html"},"ViewModel Configuration"),s(".")],-1),B=e("h3",{id:"filter-object",tabindex:"-1"},[s("Filter Object "),e("a",{class:"header-anchor",href:"#filter-object","aria-label":'Permalink to "Filter Object"'},"​")],-1),j=a(`

For each exposed scalar property on the underlying EF POCO, filter will have a corresponding property. If the filter object is set, requests made to the server to retrieve data will be passed all the values in this object via the URL's query string. These parameters will filter the resulting data to only rows where the parameter values match the row's values. For example, if filter.companyId is set to a value, only people from that company will be returned.

These parameters all allow for freeform string values, allowing the server to implement any kind of filtering logic desired. The Standard Data Source will perform the following depending on the property type:

  • Dates with a time component will be matched exactly.
  • Dates with no time component will match any dates that fell on that day.
  • Strings will match exactly unless an asterisk is found, in which case they will be matched with string.StartsWith.
  • Enums will match by string or numeric value. Multiple comma-delimited values will create a filter that will match on any of the provided values.
  • Numeric values will match exactly. Multiple comma-delimited values will create a filter that will match on any of the provided values.

Example usage:

ts
var list = new ListViewModels.PersonList();
+list.filter = { lastName: "Erickson" };
+list.load();

Static Method Members

`,6),R=e("p",null,[s("For each exposed "),e("a",{href:"/Coalesce/modeling/model-components/methods.html#static-methods"},"Static Method"),s(" on your POCO, the members outlined in "),e("a",{href:"/Coalesce/stacks/ko/client/methods.html"},"Methods - Generated TypeScript"),s(" will be created.")],-1),W=e("h3",{id:"datasources",tabindex:"-1"},[s("DataSources "),e("a",{class:"header-anchor",href:"#datasources","aria-label":'Permalink to "DataSources"'},"​")],-1),J=a('

For each of the Data Sources on the class, a corresponding class will be added to a namespace named ListViewModels.<ClassName>DataSources. This namespace can always be accessed on both ViewModel and ListViewModel instances via the dataSources property, and class instances can be assigned to the dataSource property.

',1);function $(Q,z,G,U,H,X){const t=l("Prop");return i(),r("div",null,[d,o(t,{def:"modelKeyName: string",lang:"ts"}),h,o(t,{def:"includes: string",lang:"ts"}),p,o(t,{def:"items: KnockoutObservableArray",lang:"ts"}),m,o(t,{def:"addNewItem: (): TItem",lang:"ts"}),u,o(t,{def:"deleteItem: (item: TItem): JQueryPromise",lang:"ts"}),g,o(t,{def:"queryString: string",lang:"ts"}),f,o(t,{def:"search: KnockoutObservable",lang:"ts"}),b,o(t,{def:"isLoading: KnockoutObservable",lang:"ts"}),_,o(t,{def:"isLoaded: KnockoutObservable",lang:"ts"}),y,o(t,{def:"load: (callback?: any): JQueryPromise",lang:"ts"}),w,o(t,{def:"message: KnockoutObservable",lang:"ts"}),C,o(t,{def:"getCount: (callback?: any): JQueryPromise",lang:"ts"}),v,o(t,{def:"count: KnockoutObservable",lang:"ts"}),D,o(t,{def:"totalCount: KnockoutObservable",lang:"ts"}),k,o(t,{def:"nextPage: (): void",lang:"ts"}),S,o(t,{def:"nextPageEnabled: KnockoutComputed",lang:"ts"}),T,o(t,{def:"previousPage: (): void",lang:"ts"}),M,o(t,{def:"previousPageEnabled: KnockoutComputed",lang:"ts"}),P,o(t,{def:"page: KnockoutObservable",lang:"ts"}),V,o(t,{def:"pageCount: KnockoutObservable",lang:"ts"}),L,o(t,{def:"pageSize: KnockoutObservable",lang:"ts"}),x,o(t,{def:"orderBy: KnockoutObservable",lang:"ts"}),N,O,o(t,{def:"orderByDescending: KnockoutObservable",lang:"ts"}),I,o(t,{def:"orderByToggle: (field: string): void",lang:"ts"}),A,E,K,o(t,{def:"static coalesceConfig: Coalesce.ListViewModelConfiguration",lang:"ts",id:"member-class-config"}),F,o(t,{def:"coalesceConfig: Coalesce.ListViewModelConfiguration",lang:"ts",id:"member-instance-config"}),q,B,o(t,{def:`public filter: { + personId?: string + firstName?: string + lastName?: string + gender?: string + companyId?: string +} = null;`,lang:"ts",id:"code-filter-object"}),j,o(t,{def:`public readonly namesStartingWith = new Person.NamesStartingWith(this); +public static NamesStartingWith = class NamesStartingWith extends Coalesce.ClientMethod { ... };`,lang:"ts",id:"code-static-method-members"}),R,W,o(t,{def:` +public dataSources = ListViewModels.PersonDataSources; +public dataSource: DataSource = new this.dataSources.Default();`,lang:"ts",id:"code-data-source-members"}),J])}const ee=n(c,[["render",$]]);export{Z as __pageData,ee as default}; diff --git a/assets/stacks_ko_client_list-view-model.md.sYLWdQOg.lean.js b/assets/stacks_ko_client_list-view-model.md.sYLWdQOg.lean.js new file mode 100644 index 000000000..6aa137c7e --- /dev/null +++ b/assets/stacks_ko_client_list-view-model.md.sYLWdQOg.lean.js @@ -0,0 +1,10 @@ +import{_ as n,D as l,o as i,c as r,I as o,R as a,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const Z=JSON.parse('{"title":"TypeScript ListViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/list-view-model.md","filePath":"stacks/ko/client/list-view-model.md"}'),c={name:"stacks/ko/client/list-view-model.md"},d=a("",5),h=e("p",null,"Name of the primary key of the model that this list represents.",-1),p=e("p",null,[s("String that is used to control loading and serialization on the server. See "),e("a",{href:"/Coalesce/concepts/includes.html"},"Includes String"),s(" for more information.")],-1),m=e("p",null,"The collection of items that have been loaded from the server.",-1),u=e("p",null,"Adds a new item to the items collection.",-1),g=e("p",null,"Deletes an item and removes it from the items collection.",-1),f=e("p",null,"An arbitrary URL query string to append to the API call when loading the list of items.",-1),b=e("p",null,[s("Search criteria for the list. This can be easily bound to with a text box for easy search behavior. See "),e("a",{href:"/Coalesce/modeling/model-components/attributes/search.html"},"[Search]"),s(" for a detailed look at how searching works in Coalesce.")],-1),_=e("p",null,"True if the list is loading.",-1),y=e("p",null,"True once the list has been loaded.",-1),w=e("p",null,"Load the list using current parameters for paging, searching, etc Result is placed into the items property.",-1),C=e("p",null,"If a load failed, this is a message about why it failed.",-1),v=e("p",null,"Gets the count of items without getting all the items. Result is placed into the count property.",-1),D=e("p",null,"The result of getCount(), or the total on this page.",-1),k=e("p",null,"Total count of items, even ones that are not on the page.",-1),S=e("p",null,"Change to the next page.",-1),T=e("p",null,"True if there is another page after the current page.",-1),M=e("p",null,"Change to the previous page.",-1),P=e("p",null,"True if there is another page before the current page.",-1),V=e("p",null,"Page number. This can be set to get a new page.",-1),L=e("p",null,"Total page count",-1),x=e("p",null,"Number of items on a page.",-1),N=e("p",null,"Name of a field by which this list will be loaded in ascending order.",-1),O=e("p",null,[s("If set to "),e("code",null,'"none"'),s(", default sorting behavior, including behavior defined with use of "),e("a",{href:"/Coalesce/modeling/model-components/attributes/default-order-by.html"},"[DefaultOrderBy]"),s(" in C# POCOs, is suppressed.")],-1),I=e("p",null,"Name of a field by which this list will be loaded in descending order.",-1),A=e("p",null,"Toggles sorting between ascending, descending, and no order on the specified field.",-1),E=e("h2",{id:"model-specific-members",tabindex:"-1"},[s("Model-Specific Members "),e("a",{class:"header-anchor",href:"#model-specific-members","aria-label":'Permalink to "Model-Specific Members"'},"​")],-1),K=e("h3",{id:"configuration",tabindex:"-1"},[s("Configuration "),e("a",{class:"header-anchor",href:"#configuration","aria-label":'Permalink to "Configuration"'},"​")],-1),F=e("p",null,[s("A static configuration object for configuring all instances of the ListViewModel's type is created. See "),e("a",{href:"/Coalesce/stacks/ko/client/model-config.html"},"ViewModel Configuration"),s(".")],-1),q=e("p",null,[s("An per-instance configuration object for configuring each specific ListViewModel instance is created. See "),e("a",{href:"/Coalesce/stacks/ko/client/model-config.html"},"ViewModel Configuration"),s(".")],-1),B=e("h3",{id:"filter-object",tabindex:"-1"},[s("Filter Object "),e("a",{class:"header-anchor",href:"#filter-object","aria-label":'Permalink to "Filter Object"'},"​")],-1),j=a("",6),R=e("p",null,[s("For each exposed "),e("a",{href:"/Coalesce/modeling/model-components/methods.html#static-methods"},"Static Method"),s(" on your POCO, the members outlined in "),e("a",{href:"/Coalesce/stacks/ko/client/methods.html"},"Methods - Generated TypeScript"),s(" will be created.")],-1),W=e("h3",{id:"datasources",tabindex:"-1"},[s("DataSources "),e("a",{class:"header-anchor",href:"#datasources","aria-label":'Permalink to "DataSources"'},"​")],-1),J=a("",1);function $(Q,z,G,U,H,X){const t=l("Prop");return i(),r("div",null,[d,o(t,{def:"modelKeyName: string",lang:"ts"}),h,o(t,{def:"includes: string",lang:"ts"}),p,o(t,{def:"items: KnockoutObservableArray",lang:"ts"}),m,o(t,{def:"addNewItem: (): TItem",lang:"ts"}),u,o(t,{def:"deleteItem: (item: TItem): JQueryPromise",lang:"ts"}),g,o(t,{def:"queryString: string",lang:"ts"}),f,o(t,{def:"search: KnockoutObservable",lang:"ts"}),b,o(t,{def:"isLoading: KnockoutObservable",lang:"ts"}),_,o(t,{def:"isLoaded: KnockoutObservable",lang:"ts"}),y,o(t,{def:"load: (callback?: any): JQueryPromise",lang:"ts"}),w,o(t,{def:"message: KnockoutObservable",lang:"ts"}),C,o(t,{def:"getCount: (callback?: any): JQueryPromise",lang:"ts"}),v,o(t,{def:"count: KnockoutObservable",lang:"ts"}),D,o(t,{def:"totalCount: KnockoutObservable",lang:"ts"}),k,o(t,{def:"nextPage: (): void",lang:"ts"}),S,o(t,{def:"nextPageEnabled: KnockoutComputed",lang:"ts"}),T,o(t,{def:"previousPage: (): void",lang:"ts"}),M,o(t,{def:"previousPageEnabled: KnockoutComputed",lang:"ts"}),P,o(t,{def:"page: KnockoutObservable",lang:"ts"}),V,o(t,{def:"pageCount: KnockoutObservable",lang:"ts"}),L,o(t,{def:"pageSize: KnockoutObservable",lang:"ts"}),x,o(t,{def:"orderBy: KnockoutObservable",lang:"ts"}),N,O,o(t,{def:"orderByDescending: KnockoutObservable",lang:"ts"}),I,o(t,{def:"orderByToggle: (field: string): void",lang:"ts"}),A,E,K,o(t,{def:"static coalesceConfig: Coalesce.ListViewModelConfiguration",lang:"ts",id:"member-class-config"}),F,o(t,{def:"coalesceConfig: Coalesce.ListViewModelConfiguration",lang:"ts",id:"member-instance-config"}),q,B,o(t,{def:`public filter: { + personId?: string + firstName?: string + lastName?: string + gender?: string + companyId?: string +} = null;`,lang:"ts",id:"code-filter-object"}),j,o(t,{def:`public readonly namesStartingWith = new Person.NamesStartingWith(this); +public static NamesStartingWith = class NamesStartingWith extends Coalesce.ClientMethod { ... };`,lang:"ts",id:"code-static-method-members"}),R,W,o(t,{def:` +public dataSources = ListViewModels.PersonDataSources; +public dataSource: DataSource = new this.dataSources.Default();`,lang:"ts",id:"code-data-source-members"}),J])}const ee=n(c,[["render",$]]);export{Z as __pageData,ee as default}; diff --git a/assets/stacks_ko_client_methods.md.1ReCEXt-.js b/assets/stacks_ko_client_methods.md.1ReCEXt-.js new file mode 100644 index 000000000..a13c9460b --- /dev/null +++ b/assets/stacks_ko_client_methods.md.1ReCEXt-.js @@ -0,0 +1,5 @@ +import{_ as a,D as l,o as n,c as r,I as o,R as i,k as e,a as t}from"./chunks/framework.g9eZ-ZSs.js";const E=JSON.parse('{"title":"TypeScript Method Objects","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/methods.md","filePath":"stacks/ko/client/methods.md"}'),c={name:"stacks/ko/client/methods.md"},d=i(`

TypeScript Method Objects

For each Custom Method you define, a class will be created on the corresponding TypeScript ViewModel (instance methods) or ListViewModel (static methods) that contains the properties and functions for interaction with the method. This class is accessible through a static property named after the method. An instance of this class will also be created on each instance of its parent - this instance is in a property with the camel-cased name of the method.

Here's an example for a method called Rename that takes a single parameter 'string name' and returns a string.

c#
public string Rename(string name)
+{
+    FirstName = name;
+    return FullName; // Return the new full name of the person.
+}

Base Members

The following members are available on the method object for all client methods:

`,6),h=e("p",null,"Observable that will contain the results of the method call after it is complete.",-1),p=e("p",null,"Observable with the raw, deserialized JSON result of the method call. If the method call returns an object, this will contain the deserialized JSON object from the server before it has been loaded into ViewModels and its properties loaded into observables.",-1),u=e("p",null,"Observable boolean which is true while the call to the server is pending.",-1),m=e("p",null,"If the method was not successful, this contains exception information.",-1),b=e("p",null,"Observable boolean that indicates whether the method call was successful or not.",-1),f=e("h2",{id:"listresult-t-base-members",tabindex:"-1"},[e("code",null,"ListResult"),t(" Base Members "),e("a",{class:"header-anchor",href:"#listresult-t-base-members","aria-label":'Permalink to "`ListResult` Base Members"'},"​")],-1),g=e("p",null,[t("For methods that return a "),e("code",null,"ListResult"),t(", the following additional members on the method object will be available:")],-1),_=e("p",null,"Page number of the results.",-1),w=e("p",null,"Page size of the results.",-1),y=e("p",null,"Total number of possible result pages.",-1),v=e("p",null,"Total number of results.",-1),k=e("h2",{id:"method-specific-members",tabindex:"-1"},[t("Method-specific Members "),e("a",{class:"header-anchor",href:"#method-specific-members","aria-label":'Permalink to "Method-specific Members"'},"​")],-1),D=e("p",null,[t("Declaration of the method object class. This will be generated on the parent "),e("a",{href:"./view-model.html"},"ViewModel"),t(" or "),e("a",{href:"./list-view-model.html"},"ListViewModel"),t(".")],-1),C=e("p",null,[t("Default instance of the method for easy calling of the method without needing to manually instantiate the class. This will be generated on the parent "),e("a",{href:"./view-model.html"},"ViewModel"),t(" or "),e("a",{href:"./list-view-model.html"},"ListViewModel"),t(".")],-1),O=e("p",null,[t("Function that takes all the method parameters and a callback. If "),e("code",null,"reload"),t(" is true, the ViewModel or ListViewModel that owns the method will be reloaded after the call is complete, and only after that happens will the callback be called.")],-1),T=e("p",null,"Class with one observable member per method argument for binding method arguments to user input. Only generated for methods with arguments.",-1),M=e("p",null,"Default instance of the args class. Only generated for methods with arguments.",-1),P=e("p",null,"Function for invoking the method using the args class. The default instance of the args class will be used if none is provided. Only generated for methods with arguments.",-1),R=e("p",null,[t("Simple interface using browser "),e("code",null,"prompt()"),t(" input boxes to prompt the user for the required data for the method call. The call is then made with the data provided. Only generated for methods with arguments.")],-1),j=e("p",null,[t("Observable that will contain an "),e("a",{href:"https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL",target:"_blank",rel:"noreferrer"},"Object URL"),t(" representing the last successful invocation result. Only generated for "),e("a",{href:"/Coalesce/modeling/model-components/methods.html#file-downloads"},"methods that return a file"),t(".")],-1),A=e("p",null,[t("The URL for the method. Can be useful for using as the "),e("code",null,"src"),t(" attribute of an "),e("code",null,"image"),t(" or "),e("code",null,"video"),t(" HTML element for file-downloading methods. Any arguments will be populated from "),e("code",null,"this.args"),t(". Only generated for HTTP GET methods, as configured by "),e("a",{href:"/Coalesce/modeling/model-components/attributes/controller-action.html"},"[ControllerAction]"),t(".")],-1);function S(V,L,x,K,F,N){const s=l("Prop");return n(),r("div",null,[d,o(s,{def:"public result: KnockoutObservable",lang:"ts"}),h,o(s,{def:"public rawResult: KnockoutObservable",lang:"ts"}),p,o(s,{def:"public isLoading: KnockoutObservable",lang:"ts"}),u,o(s,{def:"public message: KnockoutObservable",lang:"ts"}),m,o(s,{def:"public wasSuccessful: KnockoutObservable",lang:"ts"}),b,f,g,o(s,{def:"public page: KnockoutObservable",lang:"ts"}),_,o(s,{def:"public pageSize: KnockoutObservable",lang:"ts"}),w,o(s,{def:"public pageCount: KnockoutObservable",lang:"ts"}),y,o(s,{def:"public totalCount: KnockoutObservable",lang:"ts"}),v,k,o(s,{def:"public static Rename = class Rename extends Coalesce.ClientMethod { ... }",lang:"ts",id:"method-object-class-declaration"}),D,o(s,{def:"public readonly rename = new Person.Rename(this)",lang:"ts",id:"method-object-instance"}),C,o(s,{def:"public invoke: (name: string, callback: (result: string) => void = null, reload: boolean = true): JQueryPromise",lang:"ts"}),O,o(s,{def:"public static Args = class Args { public name: KnockoutObservable = ko.observable(null); }",lang:"ts",id:"method-args-class-declaration"}),T,o(s,{def:"public args = new Rename.Args()",lang:"ts",id:"method-args-instance"}),M,o(s,{def:"public invokeWithArgs: (args = this.args, callback?: (result: string) => void, reload: boolean = true) => JQueryPromise",lang:"ts"}),P,o(s,{def:"public invokeWithPrompts: (callback: (result: string) => void = null, reload: boolean = true) => JQueryPromise",lang:"ts"}),R,o(s,{def:"public resultObjectUrl: KnockoutObservable",lang:"ts"}),j,o(s,{def:"public url: KnockoutComputed",lang:"ts"}),A])}const I=a(c,[["render",S]]);export{E as __pageData,I as default}; diff --git a/assets/stacks_ko_client_methods.md.1ReCEXt-.lean.js b/assets/stacks_ko_client_methods.md.1ReCEXt-.lean.js new file mode 100644 index 000000000..3560d2049 --- /dev/null +++ b/assets/stacks_ko_client_methods.md.1ReCEXt-.lean.js @@ -0,0 +1 @@ +import{_ as a,D as l,o as n,c as r,I as o,R as i,k as e,a as t}from"./chunks/framework.g9eZ-ZSs.js";const E=JSON.parse('{"title":"TypeScript Method Objects","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/methods.md","filePath":"stacks/ko/client/methods.md"}'),c={name:"stacks/ko/client/methods.md"},d=i("",6),h=e("p",null,"Observable that will contain the results of the method call after it is complete.",-1),p=e("p",null,"Observable with the raw, deserialized JSON result of the method call. If the method call returns an object, this will contain the deserialized JSON object from the server before it has been loaded into ViewModels and its properties loaded into observables.",-1),u=e("p",null,"Observable boolean which is true while the call to the server is pending.",-1),m=e("p",null,"If the method was not successful, this contains exception information.",-1),b=e("p",null,"Observable boolean that indicates whether the method call was successful or not.",-1),f=e("h2",{id:"listresult-t-base-members",tabindex:"-1"},[e("code",null,"ListResult"),t(" Base Members "),e("a",{class:"header-anchor",href:"#listresult-t-base-members","aria-label":'Permalink to "`ListResult` Base Members"'},"​")],-1),g=e("p",null,[t("For methods that return a "),e("code",null,"ListResult"),t(", the following additional members on the method object will be available:")],-1),_=e("p",null,"Page number of the results.",-1),w=e("p",null,"Page size of the results.",-1),y=e("p",null,"Total number of possible result pages.",-1),v=e("p",null,"Total number of results.",-1),k=e("h2",{id:"method-specific-members",tabindex:"-1"},[t("Method-specific Members "),e("a",{class:"header-anchor",href:"#method-specific-members","aria-label":'Permalink to "Method-specific Members"'},"​")],-1),D=e("p",null,[t("Declaration of the method object class. This will be generated on the parent "),e("a",{href:"./view-model.html"},"ViewModel"),t(" or "),e("a",{href:"./list-view-model.html"},"ListViewModel"),t(".")],-1),C=e("p",null,[t("Default instance of the method for easy calling of the method without needing to manually instantiate the class. This will be generated on the parent "),e("a",{href:"./view-model.html"},"ViewModel"),t(" or "),e("a",{href:"./list-view-model.html"},"ListViewModel"),t(".")],-1),O=e("p",null,[t("Function that takes all the method parameters and a callback. If "),e("code",null,"reload"),t(" is true, the ViewModel or ListViewModel that owns the method will be reloaded after the call is complete, and only after that happens will the callback be called.")],-1),T=e("p",null,"Class with one observable member per method argument for binding method arguments to user input. Only generated for methods with arguments.",-1),M=e("p",null,"Default instance of the args class. Only generated for methods with arguments.",-1),P=e("p",null,"Function for invoking the method using the args class. The default instance of the args class will be used if none is provided. Only generated for methods with arguments.",-1),R=e("p",null,[t("Simple interface using browser "),e("code",null,"prompt()"),t(" input boxes to prompt the user for the required data for the method call. The call is then made with the data provided. Only generated for methods with arguments.")],-1),j=e("p",null,[t("Observable that will contain an "),e("a",{href:"https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL",target:"_blank",rel:"noreferrer"},"Object URL"),t(" representing the last successful invocation result. Only generated for "),e("a",{href:"/Coalesce/modeling/model-components/methods.html#file-downloads"},"methods that return a file"),t(".")],-1),A=e("p",null,[t("The URL for the method. Can be useful for using as the "),e("code",null,"src"),t(" attribute of an "),e("code",null,"image"),t(" or "),e("code",null,"video"),t(" HTML element for file-downloading methods. Any arguments will be populated from "),e("code",null,"this.args"),t(". Only generated for HTTP GET methods, as configured by "),e("a",{href:"/Coalesce/modeling/model-components/attributes/controller-action.html"},"[ControllerAction]"),t(".")],-1);function S(V,L,x,K,F,N){const s=l("Prop");return n(),r("div",null,[d,o(s,{def:"public result: KnockoutObservable",lang:"ts"}),h,o(s,{def:"public rawResult: KnockoutObservable",lang:"ts"}),p,o(s,{def:"public isLoading: KnockoutObservable",lang:"ts"}),u,o(s,{def:"public message: KnockoutObservable",lang:"ts"}),m,o(s,{def:"public wasSuccessful: KnockoutObservable",lang:"ts"}),b,f,g,o(s,{def:"public page: KnockoutObservable",lang:"ts"}),_,o(s,{def:"public pageSize: KnockoutObservable",lang:"ts"}),w,o(s,{def:"public pageCount: KnockoutObservable",lang:"ts"}),y,o(s,{def:"public totalCount: KnockoutObservable",lang:"ts"}),v,k,o(s,{def:"public static Rename = class Rename extends Coalesce.ClientMethod { ... }",lang:"ts",id:"method-object-class-declaration"}),D,o(s,{def:"public readonly rename = new Person.Rename(this)",lang:"ts",id:"method-object-instance"}),C,o(s,{def:"public invoke: (name: string, callback: (result: string) => void = null, reload: boolean = true): JQueryPromise",lang:"ts"}),O,o(s,{def:"public static Args = class Args { public name: KnockoutObservable = ko.observable(null); }",lang:"ts",id:"method-args-class-declaration"}),T,o(s,{def:"public args = new Rename.Args()",lang:"ts",id:"method-args-instance"}),M,o(s,{def:"public invokeWithArgs: (args = this.args, callback?: (result: string) => void, reload: boolean = true) => JQueryPromise",lang:"ts"}),P,o(s,{def:"public invokeWithPrompts: (callback: (result: string) => void = null, reload: boolean = true) => JQueryPromise",lang:"ts"}),R,o(s,{def:"public resultObjectUrl: KnockoutObservable",lang:"ts"}),j,o(s,{def:"public url: KnockoutComputed",lang:"ts"}),A])}const I=a(c,[["render",S]]);export{E as __pageData,I as default}; diff --git a/assets/stacks_ko_client_model-config.md.t5pvBrNy.js b/assets/stacks_ko_client_model-config.md.t5pvBrNy.js new file mode 100644 index 000000000..af22d1886 --- /dev/null +++ b/assets/stacks_ko_client_model-config.md.t5pvBrNy.js @@ -0,0 +1,8 @@ +import{_ as n,D as l,o as s,c as r,I as t,R as i,k as e,a as o}from"./chunks/framework.g9eZ-ZSs.js";const X=JSON.parse('{"title":"ViewModel Configuration","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/model-config.md","filePath":"stacks/ko/client/model-config.md"}'),c={name:"stacks/ko/client/model-config.md"},d=i('

ViewModel Configuration

A crucial part of the generated TypeScript ViewModels that Coalesce creates for you is the hierarchical configuration system that allows coarse-grained or fine-grained control over their behaviors.

Hierarchy

The configuration system has four levels where configuration can be performed, structured as follows:

Root Configuration

',5),h=i('

The root configuration contains all configuration properties which apply to class category (TypeScript ViewModels, TypeScript ListViewModels, and Services). The app property contains global app configuration that exists independent of any models. Then, for each class kind, the following are available:

Root ViewModel/ListViewModel Configuration

',2),u=e("p",null,[o("Additional root configuration objects exist, one for each class kind. These configuration objects govern behavior that applies to only objects of these types. Root configuration "),e("em",null,"can"),o(" be overridden using these objects, although the practicality of doing so is dubious.")],-1),f=e("h3",{id:"class-configuration",tabindex:"-1"},[o("Class Configuration "),e("a",{class:"header-anchor",href:"#class-configuration","aria-label":'Permalink to "Class Configuration"'},"​")],-1),p=e("p",null,[o("Each class kind has a static property named "),e("code",null,"coalesceConfig"),o(" that controls behavior for all instances of that class.")],-1),g=e("h3",{id:"instance-configuration",tabindex:"-1"},[o("Instance Configuration "),e("a",{class:"header-anchor",href:"#instance-configuration","aria-label":'Permalink to "Instance Configuration"'},"​")],-1),m=i('

Each instance of these classes also has a coalesceConfig property that controls behaviors for that instance only.

Evaluation

All configuration properties are Knockout ComputedObservable<T> objects. These observables behave like any other observable - call them with no parameter to obtain the value, call with a parameter to set their value.

Whenever a configuration property is read from, it first checks its own configuration object for the value of that property. If the explicit value for that configuration object is null, the parent's configuration will be checked for a value. This continues until either a value is found or the root configuration object is reached.

When a configuration property is given a value, that value is established on that configuration object only. Any dependent configuration objects will not be modified, and if those dependent configuration objects already have a value for that property, their existing value will be used unless that value is later set to null.

To obtain the raw value for a specific configuration property, call the raw() method on the observable: model.coalesceConfig.autoSaveEnabled.raw().

Available Properties & Defaults

The following configuration properties are available. Their default values are also listed. Note that all configuration properties are observables, but for simplicity the documentation below lists the underlying type.

Root Configuration

These properties on Coalesce.GlobalConfiguration are available to both ViewModelConfiguration, ListViewModelConfiguration, and ServiceClientConfiguration.

',10),b=e("p",null,"The relative url where the API may be found.",-1),v=e("p",null,"The relative url where the admin views may be found.",-1),_=e("p",null,[o("Whether or not the callback specified for "),e("code",null,"onFailure"),o(" will be called or not.")],-1),C=e("p",null,"A callback to be called when a failure response is received from the server.",-1),w=e("p",null,"A callback to be called when an AJAX request begins.",-1),y=e("p",null,"A callback to be called when an AJAX request completes.",-1),T=e("h3",{id:"app-configuration",tabindex:"-1"},[o("App Configuration "),e("a",{class:"header-anchor",href:"#app-configuration","aria-label":'Permalink to "App Configuration"'},"​")],-1),V=e("p",null,[o("These properties on "),e("code",null,"Coalesce.GlobalConfiguration.app"),o(" are not hierarchical - they govern the entire Coalesce application:")],-1),M=e("p",null,[o("The theme parameter to select2's constructor when called by Coalesce's select2 "),e("a",{href:"/Coalesce/stacks/ko/client/bindings.html"},"Knockout Bindings"),o(".")],-1),k=e("h3",{id:"viewmodelconfiguration",tabindex:"-1"},[o("ViewModelConfiguration "),e("a",{class:"header-anchor",href:"#viewmodelconfiguration","aria-label":'Permalink to "ViewModelConfiguration"'},"​")],-1),S=e("p",null,[o("Time to wait after a change is seen before auto-saving (if "),e("code",null,"autoSaveEnabled"),o(" is true). Acts as a debouncing timer for multiple simultaneous changes.")],-1),A=i('

An array of property names that, if set, will determine which fields will be sent to the server when saving. Only those values that are actually sent to the server will be mapped to the underlying entity.

This can improves the handling of concurrent changes being made by multiple users against different fields of the same entity. Specifically, if one page is designed to edit fields A and B, and another page is designed for editing fields C and D, you can configure this setting appropriately on each page to only save the corresponding fields.

Due to design limitations, this cannot be determined dynamically like it can with Vue's $saveMode property

WARNING

Surgical saves require DTOs on the server that are capable of determining which of their properties have been set by the model binder, as surgical saves are sent from the client by entirely omitting properties from the x-www-form-urlencoded body that is sent to the server.

The Generated C# DTOs implement the necessary logic for this; however, any Custom DTOs must have this logic manually written by you, the developer. Either implement the same pattern that can be seen in the Generated C# DTOs, or do not use surgical saves with Custom DTOs.

',4),P=e("p",null,[o("Determines whether changes to a model will be automatically saved after "),e("code",null,"saveTimeoutMs"),o(" milliseconds have elapsed.")],-1),D=e("p",null,"Determines whether or not changes to many-to-many collection properties will automatically trigger a save call to the server or not.",-1),N=e("p",null,[o("Whether to invoke "),e("code",null,"onStartBusy"),o(" and "),e("code",null,"onFinishBusy"),o(" during saves.")],-1),x=e("p",null,[o("Whether or not to reload the ViewModel with the state of the object received from the server after a call to "),e("code",null,".save()"),o(".")],-1),L=e("p",null,"Whether or not to validate the model after loading it from a DTO from the server. Disabling this can improve performance in some cases.",-1),j=e("p",null,[o("Whether or not validation on a ViewModel should be setup in its constructor, or if validation must be set up manually by calling "),e("code",null,"viewModel.setupValidation()"),o(". Turning this off can improve performance in read-only scenarios.")],-1),q=e("p",null,"An optional callback to be called when an object is loaded from a response from the server. Callback will be called after all properties on the ViewModel have been set from the server response.",-1),I=e("p",null,[o("The dataSource (either an instance or a type) that will be used as the initial dataSource when a new object of this type is created. Not valid for global configuration; recommended to be used on class-level configuration. E.g. "),e("code",null,"ViewModels.MyModel.coalesceConfig.initialDataSource(MyModel.dataSources.MyDataSource);")],-1),B=e("h3",{id:"listviewmodelconfiguration",tabindex:"-1"},[o("ListViewModelConfiguration "),e("a",{class:"header-anchor",href:"#listviewmodelconfiguration","aria-label":'Permalink to "ListViewModelConfiguration"'},"​")],-1),E=e("p",null,"No special configuration is currently available for ListViewModels.",-1),R=e("h3",{id:"serviceclientconfiguration",tabindex:"-1"},[o("ServiceClientConfiguration "),e("a",{class:"header-anchor",href:"#serviceclientconfiguration","aria-label":'Permalink to "ServiceClientConfiguration"'},"​")],-1),G=e("p",null,"No special configuration is currently available for ServiceClients.",-1);function O(F,W,$,U,J,H){const a=l("Prop");return s(),r("div",null,[d,t(a,{def:`Coalesce.GlobalConfiguration: ModelConfiguration +Coalesce.GlobalConfiguration.app: AppConfiguration`,lang:"ts",id:"code-root-config"}),h,t(a,{def:`Coalesce.GlobalConfiguration.viewModel: ViewModelConfiguration +Coalesce.GlobalConfiguration.listViewModel: ListViewModelConfiguration, BaseViewModel> +Coalesce.GlobalConfiguration.serviceClient: ServiceClientConfiguration`,lang:"ts",id:"code-global-config"}),u,f,t(a,{def:`ViewModels.ClassName.coalesceConfig: ViewModelConfiguration +ListViewModels.ClassNameList.coalesceConfig: ListViewModelConfiguration +Services.ServiceNameClient.coalesceConfig: ServiceClientConfiguration`,lang:"ts",id:"code-class-config"}),p,g,t(a,{def:`instance.coalesceConfig: ViewModelConfiguration +listInstance.coalesceConfig: ListViewModelConfiguration +serviceInstance.coalesceConfig: ServiceClientConfiguration`,lang:"ts",id:"code-instance-config"}),m,t(a,{def:"baseApiUrl: string = '/api'",lang:"ts"}),b,t(a,{def:"baseViewUrl: string = ''",lang:"ts"}),v,t(a,{def:"showFailureAlerts: boolean = true",lang:"ts"}),_,t(a,{def:"onFailure: (obj, message) => alert(message)",lang:"ts"}),C,t(a,{def:"onStartBusy: obj => Coalesce.Utilities.showBusy()",lang:"ts"}),w,t(a,{def:"onFinishBusy: obj => Coalesce.Utilities.hideBusy()",lang:"ts"}),y,T,V,t(a,{def:"select2Theme: string | null = null",lang:"ts"}),M,k,t(a,{def:"saveTimeoutMs: number = 500",lang:"ts"}),S,t(a,{def:"saveIncludedFields: string[] | null = null",lang:"ts"}),A,t(a,{def:"autoSaveEnabled: boolean = true",lang:"ts"}),P,t(a,{def:"autoSaveCollectionsEnabled: boolean = true",lang:"ts"}),D,t(a,{def:"showBusyWhenSaving: boolean = false",lang:"ts"}),N,t(a,{def:"loadResponseFromSaves: boolean = true",lang:"ts"}),x,t(a,{def:"validateOnLoadFromDto: boolean = true",lang:"ts"}),L,t(a,{def:"setupValidationAutomatically: boolean = true",lang:"ts"}),j,t(a,{def:"onLoadFromDto: null | ((object: T) => void) = null",lang:"ts"}),q,t(a,{def:"initialDataSource: null | DataSource | (new () => DataSource) = null",lang:"ts"}),I,B,E,R,G])}const z=n(c,[["render",O]]);export{X as __pageData,z as default}; diff --git a/assets/stacks_ko_client_model-config.md.t5pvBrNy.lean.js b/assets/stacks_ko_client_model-config.md.t5pvBrNy.lean.js new file mode 100644 index 000000000..b77addc06 --- /dev/null +++ b/assets/stacks_ko_client_model-config.md.t5pvBrNy.lean.js @@ -0,0 +1,8 @@ +import{_ as n,D as l,o as s,c as r,I as t,R as i,k as e,a as o}from"./chunks/framework.g9eZ-ZSs.js";const X=JSON.parse('{"title":"ViewModel Configuration","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/model-config.md","filePath":"stacks/ko/client/model-config.md"}'),c={name:"stacks/ko/client/model-config.md"},d=i("",5),h=i("",2),u=e("p",null,[o("Additional root configuration objects exist, one for each class kind. These configuration objects govern behavior that applies to only objects of these types. Root configuration "),e("em",null,"can"),o(" be overridden using these objects, although the practicality of doing so is dubious.")],-1),f=e("h3",{id:"class-configuration",tabindex:"-1"},[o("Class Configuration "),e("a",{class:"header-anchor",href:"#class-configuration","aria-label":'Permalink to "Class Configuration"'},"​")],-1),p=e("p",null,[o("Each class kind has a static property named "),e("code",null,"coalesceConfig"),o(" that controls behavior for all instances of that class.")],-1),g=e("h3",{id:"instance-configuration",tabindex:"-1"},[o("Instance Configuration "),e("a",{class:"header-anchor",href:"#instance-configuration","aria-label":'Permalink to "Instance Configuration"'},"​")],-1),m=i("",10),b=e("p",null,"The relative url where the API may be found.",-1),v=e("p",null,"The relative url where the admin views may be found.",-1),_=e("p",null,[o("Whether or not the callback specified for "),e("code",null,"onFailure"),o(" will be called or not.")],-1),C=e("p",null,"A callback to be called when a failure response is received from the server.",-1),w=e("p",null,"A callback to be called when an AJAX request begins.",-1),y=e("p",null,"A callback to be called when an AJAX request completes.",-1),T=e("h3",{id:"app-configuration",tabindex:"-1"},[o("App Configuration "),e("a",{class:"header-anchor",href:"#app-configuration","aria-label":'Permalink to "App Configuration"'},"​")],-1),V=e("p",null,[o("These properties on "),e("code",null,"Coalesce.GlobalConfiguration.app"),o(" are not hierarchical - they govern the entire Coalesce application:")],-1),M=e("p",null,[o("The theme parameter to select2's constructor when called by Coalesce's select2 "),e("a",{href:"/Coalesce/stacks/ko/client/bindings.html"},"Knockout Bindings"),o(".")],-1),k=e("h3",{id:"viewmodelconfiguration",tabindex:"-1"},[o("ViewModelConfiguration "),e("a",{class:"header-anchor",href:"#viewmodelconfiguration","aria-label":'Permalink to "ViewModelConfiguration"'},"​")],-1),S=e("p",null,[o("Time to wait after a change is seen before auto-saving (if "),e("code",null,"autoSaveEnabled"),o(" is true). Acts as a debouncing timer for multiple simultaneous changes.")],-1),A=i("",4),P=e("p",null,[o("Determines whether changes to a model will be automatically saved after "),e("code",null,"saveTimeoutMs"),o(" milliseconds have elapsed.")],-1),D=e("p",null,"Determines whether or not changes to many-to-many collection properties will automatically trigger a save call to the server or not.",-1),N=e("p",null,[o("Whether to invoke "),e("code",null,"onStartBusy"),o(" and "),e("code",null,"onFinishBusy"),o(" during saves.")],-1),x=e("p",null,[o("Whether or not to reload the ViewModel with the state of the object received from the server after a call to "),e("code",null,".save()"),o(".")],-1),L=e("p",null,"Whether or not to validate the model after loading it from a DTO from the server. Disabling this can improve performance in some cases.",-1),j=e("p",null,[o("Whether or not validation on a ViewModel should be setup in its constructor, or if validation must be set up manually by calling "),e("code",null,"viewModel.setupValidation()"),o(". Turning this off can improve performance in read-only scenarios.")],-1),q=e("p",null,"An optional callback to be called when an object is loaded from a response from the server. Callback will be called after all properties on the ViewModel have been set from the server response.",-1),I=e("p",null,[o("The dataSource (either an instance or a type) that will be used as the initial dataSource when a new object of this type is created. Not valid for global configuration; recommended to be used on class-level configuration. E.g. "),e("code",null,"ViewModels.MyModel.coalesceConfig.initialDataSource(MyModel.dataSources.MyDataSource);")],-1),B=e("h3",{id:"listviewmodelconfiguration",tabindex:"-1"},[o("ListViewModelConfiguration "),e("a",{class:"header-anchor",href:"#listviewmodelconfiguration","aria-label":'Permalink to "ListViewModelConfiguration"'},"​")],-1),E=e("p",null,"No special configuration is currently available for ListViewModels.",-1),R=e("h3",{id:"serviceclientconfiguration",tabindex:"-1"},[o("ServiceClientConfiguration "),e("a",{class:"header-anchor",href:"#serviceclientconfiguration","aria-label":'Permalink to "ServiceClientConfiguration"'},"​")],-1),G=e("p",null,"No special configuration is currently available for ServiceClients.",-1);function O(F,W,$,U,J,H){const a=l("Prop");return s(),r("div",null,[d,t(a,{def:`Coalesce.GlobalConfiguration: ModelConfiguration +Coalesce.GlobalConfiguration.app: AppConfiguration`,lang:"ts",id:"code-root-config"}),h,t(a,{def:`Coalesce.GlobalConfiguration.viewModel: ViewModelConfiguration +Coalesce.GlobalConfiguration.listViewModel: ListViewModelConfiguration, BaseViewModel> +Coalesce.GlobalConfiguration.serviceClient: ServiceClientConfiguration`,lang:"ts",id:"code-global-config"}),u,f,t(a,{def:`ViewModels.ClassName.coalesceConfig: ViewModelConfiguration +ListViewModels.ClassNameList.coalesceConfig: ListViewModelConfiguration +Services.ServiceNameClient.coalesceConfig: ServiceClientConfiguration`,lang:"ts",id:"code-class-config"}),p,g,t(a,{def:`instance.coalesceConfig: ViewModelConfiguration +listInstance.coalesceConfig: ListViewModelConfiguration +serviceInstance.coalesceConfig: ServiceClientConfiguration`,lang:"ts",id:"code-instance-config"}),m,t(a,{def:"baseApiUrl: string = '/api'",lang:"ts"}),b,t(a,{def:"baseViewUrl: string = ''",lang:"ts"}),v,t(a,{def:"showFailureAlerts: boolean = true",lang:"ts"}),_,t(a,{def:"onFailure: (obj, message) => alert(message)",lang:"ts"}),C,t(a,{def:"onStartBusy: obj => Coalesce.Utilities.showBusy()",lang:"ts"}),w,t(a,{def:"onFinishBusy: obj => Coalesce.Utilities.hideBusy()",lang:"ts"}),y,T,V,t(a,{def:"select2Theme: string | null = null",lang:"ts"}),M,k,t(a,{def:"saveTimeoutMs: number = 500",lang:"ts"}),S,t(a,{def:"saveIncludedFields: string[] | null = null",lang:"ts"}),A,t(a,{def:"autoSaveEnabled: boolean = true",lang:"ts"}),P,t(a,{def:"autoSaveCollectionsEnabled: boolean = true",lang:"ts"}),D,t(a,{def:"showBusyWhenSaving: boolean = false",lang:"ts"}),N,t(a,{def:"loadResponseFromSaves: boolean = true",lang:"ts"}),x,t(a,{def:"validateOnLoadFromDto: boolean = true",lang:"ts"}),L,t(a,{def:"setupValidationAutomatically: boolean = true",lang:"ts"}),j,t(a,{def:"onLoadFromDto: null | ((object: T) => void) = null",lang:"ts"}),q,t(a,{def:"initialDataSource: null | DataSource | (new () => DataSource) = null",lang:"ts"}),I,B,E,R,G])}const z=n(c,[["render",O]]);export{X as __pageData,z as default}; diff --git a/assets/stacks_ko_client_view-model.md.cA7v0DUf.js b/assets/stacks_ko_client_view-model.md.cA7v0DUf.js new file mode 100644 index 000000000..522dd6360 --- /dev/null +++ b/assets/stacks_ko_client_view-model.md.cA7v0DUf.js @@ -0,0 +1,21 @@ +import{_ as s,D as l,o as i,c as r,I as a,R as n,k as e,a as t}from"./chunks/framework.g9eZ-ZSs.js";const ye=JSON.parse('{"title":"TypeScript ViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/view-model.md","filePath":"stacks/ko/client/view-model.md"}'),d={name:"stacks/ko/client/view-model.md"},c=n('

TypeScript ViewModels

For each database-mapped type in your model, Coalesce will generate a TypeScript class that provides a multitude of functionality for interacting with the data on the client.

These ViewModels are dependent on Knockout, and are designed to be used directly from Knockout bindings in your HTML. All data properties on the generated model are Knockout observables.

Base Members

The following base members are available to all generated ViewModel classes:

',5),h=e("p",null,[t("String that will be passed to the server when loading and saving that allows for data trimming via C# Attributes. See "),e("a",{href:"/Coalesce/concepts/includes.html"},"Includes String"),t(".")],-1),u=e("p",null,"Flag to use to determine if this item is checked. Only provided for convenience.",-1),p=e("p",null,"Flag to use to determine if this item is selected. Only provided for convenience.",-1),b=e("p",null,"Flag to use to determine if this item is being edited. Only provided for convenience.",-1),f=e("p",null,[t("Toggles the "),e("code",null,"isEditing"),t(" flag.")],-1),m=e("p",null,"Flag to use to determine if this item is expanded. Only provided for convenience.",-1),g=e("p",null,[t("Toggles the "),e("code",null,"isExpanded"),t(" flag.")],-1),_=e("p",null,"Flag to use to determine if this item is shown. Only provided for convenience.",-1),v=e("p",null,[t("Toggles the "),e("code",null,"isSelected"),t(" flag.")],-1),y=e("p",null,"Sets isSelected(true) on this object and clears on the rest of the items in the parent collection.",-1),w=e("p",null,"Dirty Flag. Set when a value on the model changes. Reset when the model is saved or reloaded.",-1),k=e("p",null,"True once the data has been loaded.",-1),C=e("p",null,"True if the object is loading.",-1),T=e("p",null,"True if the object is currently saving.",-1),S=e("p",null,"Returns true if the current object, or any of its children, are saving.",-1),M=e("p",null,"Loads the object from the server based on the id specified. If no id is specified, the current id, is used if one is set.",-1),P=e("p",null,"Loads any child objects that have an ID set, but not the full object. This is useful when creating an object that has a parent object and the ID is set on the new child.",-1),O=e("p",null,"Loads this object from a data transfer object received from the server.",-1),V=e("ul",null,[e("li",null,[e("code",null,"force"),t(" - Will override the check against isLoading that is done to prevent recursion.")]),e("li",null,[e("code",null,"allowCollectionDeletes"),t(" - Set true when entire collections are loaded. True is the default. In some cases only a partial collection is returned, set to false to only add/update collections.")])],-1),K=e("p",null,"Deletes the object without any prompt for confirmation.",-1),j=e("p",null,"Deletes the object if a prompt for confirmation is answered affirmatively.",-1),x=e("p",null,"Contains the error message from the last failed call to the server.",-1),D=e("p",null,[t("Register a callback to be called when a save is done. Returns "),e("code",null,"true"),t(" if the callback was registered, or "),e("code",null,"false"),t(" if the callback was already registered.")],-1),A=e("p",null,"Saves this object into a data transfer object to send to the server.",-1),I=e("p",null,"Saves the object to the server and then calls a callback. Returns false if there are validation errors.",-1),F=e("p",null,"Parent of this object, if this object was loaded as part of a hierarchy.",-1),E=e("p",null,"Parent of this object, if this object was loaded as part of list of objects.",-1),L=e("p",null,"URL to a stock editor for this object.",-1),N=e("p",null,"Displays an editor for the object in a modal dialog.",-1),R=e("p",null,"Triggers any validation messages to be shown, and returns a bool that indicates if there are any validation errors.",-1),B=e("p",null,"ValidationIssues returned from the server when trying to persist data",-1),q=e("p",null,"List of warnings found during validation. Saving is still allowed with warnings present.",-1),J=e("p",null,"List of errors found during validation. Any errors present will prevent saving.",-1),G=e("h2",{id:"model-specific-members",tabindex:"-1"},[t("Model-Specific Members "),e("a",{class:"header-anchor",href:"#model-specific-members","aria-label":'Permalink to "Model-Specific Members"'},"​")],-1),H=e("p",null,[t("The following members are generated for each generated ViewModel class and are unique to each class. The examples below are based on a type named "),e("code",null,"Person"),t(".")],-1),Q=e("h3",{id:"configuration",tabindex:"-1"},[t("Configuration "),e("a",{class:"header-anchor",href:"#configuration","aria-label":'Permalink to "Configuration"'},"​")],-1),$=e("p",null,[t("A static configuration object for configuring all instances of the ViewModel's type is created. See "),e("a",{href:"/Coalesce/stacks/ko/client/model-config.html"},"ViewModel Configuration"),t(".")],-1),U=e("p",null,[t("An per-instance configuration object for configuring each specific ViewModel instance is created. See "),e("a",{href:"/Coalesce/stacks/ko/client/model-config.html"},"ViewModel Configuration"),t(".")],-1),W=e("h3",{id:"datasources",tabindex:"-1"},[t("DataSources "),e("a",{class:"header-anchor",href:"#datasources","aria-label":'Permalink to "DataSources"'},"​")],-1),z=n('

For each of the Data Sources for a model, a class will be added to a namespace named ListViewModels.<ClassName>DataSources. This namespace can always be accessed on both ViewModel and ListViewModel instances via the dataSources property, and class instances can be assigned to the dataSource property.

Data Properties

',2),X=e("p",null,[t("For each exposed property on the underlying EF POCO, a "),e("code",null,"KnockoutObservable"),t(" property will exist on the TypeScript model. For navigation properties, these will be typed with the corresponding TypeScript ViewModel for the other end of the relationship. For collections (including collection navigation properties), these properties will be "),e("code",null,"KnockoutObservableArray"),t(" objects.")],-1),Y=e("h3",{id:"enum-members",tabindex:"-1"},[t("Enum Members "),e("a",{class:"header-anchor",href:"#enum-members","aria-label":'Permalink to "Enum Members"'},"​")],-1),Z=e("p",null,[t("For each "),e("code",null,"enum"),t(" property on your POCO, the following will be created:")],-1),ee=e("p",null,[t("A "),e("code",null,"KnockoutComputed"),t(" property that will provide the text to display for that property.")],-1),te=e("p",null,[t("A static array of objects with properties "),e("code",null,"id"),t(" and "),e("code",null,"value"),t(" that represent all the values of the enum.")],-1),oe=e("p",null,[t("A TypeScript enum that mirrors the C# enum directly. This enum is in a sub-namespace of "),e("code",null,"ViewModels"),t(" named the same as the class name.")],-1),ae=e("h3",{id:"collection-navigation-property-helpers",tabindex:"-1"},[t("Collection Navigation Property Helpers "),e("a",{class:"header-anchor",href:"#collection-navigation-property-helpers","aria-label":'Permalink to "Collection Navigation Property Helpers"'},"​")],-1),ne=e("p",null,"For each collection navigation property on the POCO, the following members will be created:",-1),se=e("p",null,[t("A method that will add a new object to that collection property. If "),e("code",null,"autoSave"),t(" is specified, the auto-save behavior of the new object will be set to that value. Otherwise, the inherited default will be used (see "),e("a",{href:"/Coalesce/stacks/ko/client/model-config.html"},"ViewModel Configuration"),t(")")],-1),le=e("p",null,[t("A "),e("code",null,"KnockoutComputed"),t(" that evaluates to a relative url for the generated table view that contains only the items that belong to the collection navigation property.")],-1),ie=e("h3",{id:"reference-navigation-property-helpers",tabindex:"-1"},[t("Reference Navigation Property Helpers "),e("a",{class:"header-anchor",href:"#reference-navigation-property-helpers","aria-label":'Permalink to "Reference Navigation Property Helpers"'},"​")],-1),re=e("p",null,[t("For each reference navigation property on the POCO a method will be created that will call "),e("code",null,"showEditor"),t(" on that current value of the navigation property, or on a new instance if the current value is null.")],-1),de=e("p",null,[t("For each reference navigation property, a "),e("code",null,"KnockoutComputed"),t(" property will be created that will provide the text to display for that property. This will be the property on the class annotated with "),e("a",{href:"/Coalesce/modeling/model-components/attributes/list-text.html"},"[ListText]"),t(".")],-1),ce=e("h3",{id:"instance-method-members",tabindex:"-1"},[t("Instance Method Members "),e("a",{class:"header-anchor",href:"#instance-method-members","aria-label":'Permalink to "Instance Method Members"'},"​")],-1),he=e("p",null,[t("For each "),e("a",{href:"/Coalesce/modeling/model-components/methods.html"},"Instance Method"),t(" on your POCO, a class and instance member will be created as described in "),e("a",{href:"/Coalesce/stacks/ko/client/methods.html"},"Methods - Generated TypeScript"),t(".")],-1);function ue(pe,be,fe,me,ge,_e){const o=l("Prop");return i(),r("div",null,[c,a(o,{def:"includes: string",lang:"ts"}),h,a(o,{def:"isChecked: KnockoutObservable",lang:"ts"}),u,a(o,{def:"isSelected: KnockoutObservable",lang:"ts"}),p,a(o,{def:"isEditing: KnockoutObservable",lang:"ts"}),b,a(o,{def:"toggleIsEditing () => void",lang:"ts"}),f,a(o,{def:"isExpanded: KnockoutObservable",lang:"ts"}),m,a(o,{def:"toggleIsExpanded: () => void",lang:"ts"}),g,a(o,{def:"isVisible: KnockoutObservable",lang:"ts"}),_,a(o,{def:"toggleIsSelected () => void",lang:"ts"}),v,a(o,{def:"selectSingle: (): boolean",lang:"ts"}),y,a(o,{def:"isDirty: KnockoutObservable",lang:"ts"}),w,a(o,{def:"isLoaded: KnockoutObservable",lang:"ts"}),k,a(o,{def:"isLoading: KnockoutObservable",lang:"ts"}),C,a(o,{def:"isSaving: KnockoutObservable",lang:"ts"}),T,a(o,{def:"isThisOrChildSaving: KnockoutComputed",lang:"ts"}),S,a(o,{def:"load: id: any, callback?: (self: T) => void): JQueryPromise | undefined",lang:"ts"}),M,a(o,{def:"loadChildren: callback?: () => void) => void",lang:"ts"}),P,a(o,{def:"loadFromDto: data: any, force?: boolean, allowCollectionDeletes?: boolean) => void",lang:"ts"}),O,V,a(o,{def:"deleteItem: callback?: (self: T) => void): JQueryPromise | undefined",lang:"ts"}),K,a(o,{def:"deleteItemWithConfirmation: callback?: () => void, message?: string): JQueryPromise | undefined",lang:"ts"}),j,a(o,{def:"errorMessage: KnockoutObservable",lang:"ts"}),x,a(o,{def:"onSave: callback: (self: T) => void): boolean",lang:"ts"}),D,a(o,{def:"saveToDto: () => any",lang:"ts"}),A,a(o,{def:"save: callback?: (self: T) => void): JQueryPromise | boolean | undefined",lang:"ts"}),I,a(o,{def:"parent: any",lang:"ts"}),F,a(o,{def:"parentCollection: KnockoutObservableArray",lang:"ts"}),E,a(o,{def:"editUrl: KnockoutComputed",lang:"ts"}),L,a(o,{def:"showEditor: callback?: any): JQueryPromise",lang:"ts"}),N,a(o,{def:"validate: (): boolean",lang:"ts"}),R,a(o,{def:"validationIssues: any",lang:"ts"}),B,a(o,{def:"warnings: KnockoutValidationErrors",lang:"ts"}),q,a(o,{def:"errors: KnockoutValidationErrors",lang:"ts"}),J,G,H,Q,a(o,{def:"static coalesceConfig: Coalesce.ViewModelConfiguration",lang:"ts",id:"member-class-config"}),$,a(o,{def:"coalesceConfig: Coalesce.ViewModelConfiguration",lang:"ts",id:"member-instance-config"}),U,W,a(o,{def:` +public dataSources = ListViewModels.PersonDataSources; +public dataSource: DataSource = new this.dataSources.Default();`,lang:"ts",id:"code-data-source-members"}),z,a(o,{def:` +public personId: KnockoutObservable = ko.observable(null); +public fullName: KnockoutObservable = ko.observable(null); +public gender: KnockoutObservable = ko.observable(null); +public companyId: KnockoutObservable = ko.observable(null); +public company: KnockoutObservable = ko.observable(null); +public addresses: KnockoutObservableArray = ko.observableArray([]); +public birthDate: KnockoutObservable = ko.observable(moment());`,lang:"ts",id:"code-data-members"}),X,Y,Z,a(o,{def:"public genderText: KnockoutComputed",lang:"ts"}),ee,a(o,{def:`public genderValues: Coalesce.EnumValue[] = [ + { id: 1, value: 'Male' }, + { id: 2, value: 'Female' }, + { id: 3, value: 'Other' }, +];`,lang:"ts",id:"code-enum-members"}),te,a(o,{def:`export namespace Person { + export enum GenderEnum { + Male = 1, + Female = 2, + Other = 3, + }; +}`,lang:"ts","no-class":"",id:"code-enum-def"}),oe,ae,ne,a(o,{def:"public addToAddresses: (autoSave?: boolean) => ViewModels.Address;",lang:"ts"}),se,a(o,{def:"public addressesListUrl: KnockoutComputed;",lang:"ts"}),le,ie,a(o,{def:"public showCompanyEditor: (callback?: any) => void;",lang:"ts"}),re,a(o,{def:"public companyText: KnockoutComputed;",lang:"ts"}),de,ce,a(o,{def:`public readonly getBirthDate = new Person.GetBirthDate(this); +public static GetBirthDate = class GetBirthDate extends Coalesce.ClientMethod { ... };`,lang:"ts",id:"code-instance-method-members"}),he])}const we=s(d,[["render",ue]]);export{ye as __pageData,we as default}; diff --git a/assets/stacks_ko_client_view-model.md.cA7v0DUf.lean.js b/assets/stacks_ko_client_view-model.md.cA7v0DUf.lean.js new file mode 100644 index 000000000..dc6542bd9 --- /dev/null +++ b/assets/stacks_ko_client_view-model.md.cA7v0DUf.lean.js @@ -0,0 +1,21 @@ +import{_ as s,D as l,o as i,c as r,I as a,R as n,k as e,a as t}from"./chunks/framework.g9eZ-ZSs.js";const ye=JSON.parse('{"title":"TypeScript ViewModels","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/client/view-model.md","filePath":"stacks/ko/client/view-model.md"}'),d={name:"stacks/ko/client/view-model.md"},c=n("",5),h=e("p",null,[t("String that will be passed to the server when loading and saving that allows for data trimming via C# Attributes. See "),e("a",{href:"/Coalesce/concepts/includes.html"},"Includes String"),t(".")],-1),u=e("p",null,"Flag to use to determine if this item is checked. Only provided for convenience.",-1),p=e("p",null,"Flag to use to determine if this item is selected. Only provided for convenience.",-1),b=e("p",null,"Flag to use to determine if this item is being edited. Only provided for convenience.",-1),f=e("p",null,[t("Toggles the "),e("code",null,"isEditing"),t(" flag.")],-1),m=e("p",null,"Flag to use to determine if this item is expanded. Only provided for convenience.",-1),g=e("p",null,[t("Toggles the "),e("code",null,"isExpanded"),t(" flag.")],-1),_=e("p",null,"Flag to use to determine if this item is shown. Only provided for convenience.",-1),v=e("p",null,[t("Toggles the "),e("code",null,"isSelected"),t(" flag.")],-1),y=e("p",null,"Sets isSelected(true) on this object and clears on the rest of the items in the parent collection.",-1),w=e("p",null,"Dirty Flag. Set when a value on the model changes. Reset when the model is saved or reloaded.",-1),k=e("p",null,"True once the data has been loaded.",-1),C=e("p",null,"True if the object is loading.",-1),T=e("p",null,"True if the object is currently saving.",-1),S=e("p",null,"Returns true if the current object, or any of its children, are saving.",-1),M=e("p",null,"Loads the object from the server based on the id specified. If no id is specified, the current id, is used if one is set.",-1),P=e("p",null,"Loads any child objects that have an ID set, but not the full object. This is useful when creating an object that has a parent object and the ID is set on the new child.",-1),O=e("p",null,"Loads this object from a data transfer object received from the server.",-1),V=e("ul",null,[e("li",null,[e("code",null,"force"),t(" - Will override the check against isLoading that is done to prevent recursion.")]),e("li",null,[e("code",null,"allowCollectionDeletes"),t(" - Set true when entire collections are loaded. True is the default. In some cases only a partial collection is returned, set to false to only add/update collections.")])],-1),K=e("p",null,"Deletes the object without any prompt for confirmation.",-1),j=e("p",null,"Deletes the object if a prompt for confirmation is answered affirmatively.",-1),x=e("p",null,"Contains the error message from the last failed call to the server.",-1),D=e("p",null,[t("Register a callback to be called when a save is done. Returns "),e("code",null,"true"),t(" if the callback was registered, or "),e("code",null,"false"),t(" if the callback was already registered.")],-1),A=e("p",null,"Saves this object into a data transfer object to send to the server.",-1),I=e("p",null,"Saves the object to the server and then calls a callback. Returns false if there are validation errors.",-1),F=e("p",null,"Parent of this object, if this object was loaded as part of a hierarchy.",-1),E=e("p",null,"Parent of this object, if this object was loaded as part of list of objects.",-1),L=e("p",null,"URL to a stock editor for this object.",-1),N=e("p",null,"Displays an editor for the object in a modal dialog.",-1),R=e("p",null,"Triggers any validation messages to be shown, and returns a bool that indicates if there are any validation errors.",-1),B=e("p",null,"ValidationIssues returned from the server when trying to persist data",-1),q=e("p",null,"List of warnings found during validation. Saving is still allowed with warnings present.",-1),J=e("p",null,"List of errors found during validation. Any errors present will prevent saving.",-1),G=e("h2",{id:"model-specific-members",tabindex:"-1"},[t("Model-Specific Members "),e("a",{class:"header-anchor",href:"#model-specific-members","aria-label":'Permalink to "Model-Specific Members"'},"​")],-1),H=e("p",null,[t("The following members are generated for each generated ViewModel class and are unique to each class. The examples below are based on a type named "),e("code",null,"Person"),t(".")],-1),Q=e("h3",{id:"configuration",tabindex:"-1"},[t("Configuration "),e("a",{class:"header-anchor",href:"#configuration","aria-label":'Permalink to "Configuration"'},"​")],-1),$=e("p",null,[t("A static configuration object for configuring all instances of the ViewModel's type is created. See "),e("a",{href:"/Coalesce/stacks/ko/client/model-config.html"},"ViewModel Configuration"),t(".")],-1),U=e("p",null,[t("An per-instance configuration object for configuring each specific ViewModel instance is created. See "),e("a",{href:"/Coalesce/stacks/ko/client/model-config.html"},"ViewModel Configuration"),t(".")],-1),W=e("h3",{id:"datasources",tabindex:"-1"},[t("DataSources "),e("a",{class:"header-anchor",href:"#datasources","aria-label":'Permalink to "DataSources"'},"​")],-1),z=n("",2),X=e("p",null,[t("For each exposed property on the underlying EF POCO, a "),e("code",null,"KnockoutObservable"),t(" property will exist on the TypeScript model. For navigation properties, these will be typed with the corresponding TypeScript ViewModel for the other end of the relationship. For collections (including collection navigation properties), these properties will be "),e("code",null,"KnockoutObservableArray"),t(" objects.")],-1),Y=e("h3",{id:"enum-members",tabindex:"-1"},[t("Enum Members "),e("a",{class:"header-anchor",href:"#enum-members","aria-label":'Permalink to "Enum Members"'},"​")],-1),Z=e("p",null,[t("For each "),e("code",null,"enum"),t(" property on your POCO, the following will be created:")],-1),ee=e("p",null,[t("A "),e("code",null,"KnockoutComputed"),t(" property that will provide the text to display for that property.")],-1),te=e("p",null,[t("A static array of objects with properties "),e("code",null,"id"),t(" and "),e("code",null,"value"),t(" that represent all the values of the enum.")],-1),oe=e("p",null,[t("A TypeScript enum that mirrors the C# enum directly. This enum is in a sub-namespace of "),e("code",null,"ViewModels"),t(" named the same as the class name.")],-1),ae=e("h3",{id:"collection-navigation-property-helpers",tabindex:"-1"},[t("Collection Navigation Property Helpers "),e("a",{class:"header-anchor",href:"#collection-navigation-property-helpers","aria-label":'Permalink to "Collection Navigation Property Helpers"'},"​")],-1),ne=e("p",null,"For each collection navigation property on the POCO, the following members will be created:",-1),se=e("p",null,[t("A method that will add a new object to that collection property. If "),e("code",null,"autoSave"),t(" is specified, the auto-save behavior of the new object will be set to that value. Otherwise, the inherited default will be used (see "),e("a",{href:"/Coalesce/stacks/ko/client/model-config.html"},"ViewModel Configuration"),t(")")],-1),le=e("p",null,[t("A "),e("code",null,"KnockoutComputed"),t(" that evaluates to a relative url for the generated table view that contains only the items that belong to the collection navigation property.")],-1),ie=e("h3",{id:"reference-navigation-property-helpers",tabindex:"-1"},[t("Reference Navigation Property Helpers "),e("a",{class:"header-anchor",href:"#reference-navigation-property-helpers","aria-label":'Permalink to "Reference Navigation Property Helpers"'},"​")],-1),re=e("p",null,[t("For each reference navigation property on the POCO a method will be created that will call "),e("code",null,"showEditor"),t(" on that current value of the navigation property, or on a new instance if the current value is null.")],-1),de=e("p",null,[t("For each reference navigation property, a "),e("code",null,"KnockoutComputed"),t(" property will be created that will provide the text to display for that property. This will be the property on the class annotated with "),e("a",{href:"/Coalesce/modeling/model-components/attributes/list-text.html"},"[ListText]"),t(".")],-1),ce=e("h3",{id:"instance-method-members",tabindex:"-1"},[t("Instance Method Members "),e("a",{class:"header-anchor",href:"#instance-method-members","aria-label":'Permalink to "Instance Method Members"'},"​")],-1),he=e("p",null,[t("For each "),e("a",{href:"/Coalesce/modeling/model-components/methods.html"},"Instance Method"),t(" on your POCO, a class and instance member will be created as described in "),e("a",{href:"/Coalesce/stacks/ko/client/methods.html"},"Methods - Generated TypeScript"),t(".")],-1);function ue(pe,be,fe,me,ge,_e){const o=l("Prop");return i(),r("div",null,[c,a(o,{def:"includes: string",lang:"ts"}),h,a(o,{def:"isChecked: KnockoutObservable",lang:"ts"}),u,a(o,{def:"isSelected: KnockoutObservable",lang:"ts"}),p,a(o,{def:"isEditing: KnockoutObservable",lang:"ts"}),b,a(o,{def:"toggleIsEditing () => void",lang:"ts"}),f,a(o,{def:"isExpanded: KnockoutObservable",lang:"ts"}),m,a(o,{def:"toggleIsExpanded: () => void",lang:"ts"}),g,a(o,{def:"isVisible: KnockoutObservable",lang:"ts"}),_,a(o,{def:"toggleIsSelected () => void",lang:"ts"}),v,a(o,{def:"selectSingle: (): boolean",lang:"ts"}),y,a(o,{def:"isDirty: KnockoutObservable",lang:"ts"}),w,a(o,{def:"isLoaded: KnockoutObservable",lang:"ts"}),k,a(o,{def:"isLoading: KnockoutObservable",lang:"ts"}),C,a(o,{def:"isSaving: KnockoutObservable",lang:"ts"}),T,a(o,{def:"isThisOrChildSaving: KnockoutComputed",lang:"ts"}),S,a(o,{def:"load: id: any, callback?: (self: T) => void): JQueryPromise | undefined",lang:"ts"}),M,a(o,{def:"loadChildren: callback?: () => void) => void",lang:"ts"}),P,a(o,{def:"loadFromDto: data: any, force?: boolean, allowCollectionDeletes?: boolean) => void",lang:"ts"}),O,V,a(o,{def:"deleteItem: callback?: (self: T) => void): JQueryPromise | undefined",lang:"ts"}),K,a(o,{def:"deleteItemWithConfirmation: callback?: () => void, message?: string): JQueryPromise | undefined",lang:"ts"}),j,a(o,{def:"errorMessage: KnockoutObservable",lang:"ts"}),x,a(o,{def:"onSave: callback: (self: T) => void): boolean",lang:"ts"}),D,a(o,{def:"saveToDto: () => any",lang:"ts"}),A,a(o,{def:"save: callback?: (self: T) => void): JQueryPromise | boolean | undefined",lang:"ts"}),I,a(o,{def:"parent: any",lang:"ts"}),F,a(o,{def:"parentCollection: KnockoutObservableArray",lang:"ts"}),E,a(o,{def:"editUrl: KnockoutComputed",lang:"ts"}),L,a(o,{def:"showEditor: callback?: any): JQueryPromise",lang:"ts"}),N,a(o,{def:"validate: (): boolean",lang:"ts"}),R,a(o,{def:"validationIssues: any",lang:"ts"}),B,a(o,{def:"warnings: KnockoutValidationErrors",lang:"ts"}),q,a(o,{def:"errors: KnockoutValidationErrors",lang:"ts"}),J,G,H,Q,a(o,{def:"static coalesceConfig: Coalesce.ViewModelConfiguration",lang:"ts",id:"member-class-config"}),$,a(o,{def:"coalesceConfig: Coalesce.ViewModelConfiguration",lang:"ts",id:"member-instance-config"}),U,W,a(o,{def:` +public dataSources = ListViewModels.PersonDataSources; +public dataSource: DataSource = new this.dataSources.Default();`,lang:"ts",id:"code-data-source-members"}),z,a(o,{def:` +public personId: KnockoutObservable = ko.observable(null); +public fullName: KnockoutObservable = ko.observable(null); +public gender: KnockoutObservable = ko.observable(null); +public companyId: KnockoutObservable = ko.observable(null); +public company: KnockoutObservable = ko.observable(null); +public addresses: KnockoutObservableArray = ko.observableArray([]); +public birthDate: KnockoutObservable = ko.observable(moment());`,lang:"ts",id:"code-data-members"}),X,Y,Z,a(o,{def:"public genderText: KnockoutComputed",lang:"ts"}),ee,a(o,{def:`public genderValues: Coalesce.EnumValue[] = [ + { id: 1, value: 'Male' }, + { id: 2, value: 'Female' }, + { id: 3, value: 'Other' }, +];`,lang:"ts",id:"code-enum-members"}),te,a(o,{def:`export namespace Person { + export enum GenderEnum { + Male = 1, + Female = 2, + Other = 3, + }; +}`,lang:"ts","no-class":"",id:"code-enum-def"}),oe,ae,ne,a(o,{def:"public addToAddresses: (autoSave?: boolean) => ViewModels.Address;",lang:"ts"}),se,a(o,{def:"public addressesListUrl: KnockoutComputed;",lang:"ts"}),le,ie,a(o,{def:"public showCompanyEditor: (callback?: any) => void;",lang:"ts"}),re,a(o,{def:"public companyText: KnockoutComputed;",lang:"ts"}),de,ce,a(o,{def:`public readonly getBirthDate = new Person.GetBirthDate(this); +public static GetBirthDate = class GetBirthDate extends Coalesce.ClientMethod { ... };`,lang:"ts",id:"code-instance-method-members"}),he])}const we=s(d,[["render",ue]]);export{ye as __pageData,we as default}; diff --git a/assets/stacks_ko_getting-started.md.KI5cX2XQ.js b/assets/stacks_ko_getting-started.md.KI5cX2XQ.js new file mode 100644 index 000000000..f6536f05a --- /dev/null +++ b/assets/stacks_ko_getting-started.md.KI5cX2XQ.js @@ -0,0 +1,51 @@ +import{_ as s,o as a,c as n,R as l}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"Getting Started with Knockout","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/getting-started.md","filePath":"stacks/ko/getting-started.md"}'),o={name:"stacks/ko/getting-started.md"},e=l(`

Getting Started with Knockout

Creating a Project

WARNING

The Coalesce Knockout.js stack is deprecated and will be receiving only critical bug fixes going forward. You are strongly encouraged to start all new Coalesce projects with Vue.

The quickest and easiest way to create a new Coalesce Knockout application is to use the dotnet new template. In your favorite shell:

sh
dotnet new install IntelliTect.Coalesce.KnockoutJS.Template
+dotnet new coalesceko

View on GitHub

Data Modeling

At this point, you can open up the newly-created solution in Visual Studio and run your application. However, your application won't do much without a data model, so you will probably want to do the following before running:

  • Create an initial Data Model by adding EF entity classes to the data project and the corresponding DbSet<> properties to AppDbContext. You will notice that the starter project includes a single model, Widget, to start with. Feel free to change this model or remove it entirely. Read Entity Models for more information about creating a data model.

  • Run dotnet ef migrations add Init (Init can be any name) in the data project to create an initial database migration.

  • Run Coalesce's code generation by either:

    • Running dotnet coalesce in the web project's root directory
    • Running the coalesce npm script (Vue) or gulp task (Knockout) in the Task Runner Explorer

You're now at a point where you can start creating your own pages!

Building Pages & Features

Lets say we've created a model called Person as follows, and we've ran code generation with dotnet coalesce:

c#
namespace MyApplication.Data.Models 
+{
+    public class Person
+    {
+        public int PersonId { get; set; }
+        public string Name { get; set; }
+        public DateTimeOffset? BirthDate { get; set; }
+    }
+}

We can create a details page for a Person by creating:

  • A controller in src/MyApplication.Web/Controllers/PersonController.cs:

    c#
    namespace MyApplication.Web.Controllers
    +{
    +    public partial class PersonController
    +    {
    +        public IActionResult Details() => View();
    +    }
    +}
  • A view in src/MyApplication.Web/Views/Person/Details.cshtml:

    razor
    <h1>Person Details</h1>
    +
    +<div data-bind="with: person">
    +    <dl class="dl-horizontal">
    +        <dt>Name </dt>
    +        <dd data-bind="text: name"></dd>
    +
    +        <dt>Date of Birth </dt>
    +        <dd data-bind="moment: birthDate, format: 'MM/DD/YYYY hh:mm a'"></dd>
    +    </dl>
    +</div>
    +
    +@section Scripts
    +{
    +<script src="~/js/person.details.js"></script>
    +<script>
    +    $(function () {
    +        var vm = new MyApplication.PersonDetails();
    +        ko.applyBindings(vm);
    +        vm.load();
    +    });
    +</script>
    +}
  • And a script in src/MyApplication.Web/Scripts/person.details.ts:

    ts
    /// <reference path="viewmodels.generated.d.ts" />
    +
    +module MyApplication {
    +    export class PersonDetails {
    +        public person = new ViewModels.Person();
    +
    +        load() {
    +            var id = Coalesce.Utilities.GetUrlParameter("id");
    +            if (id != null && id != '') {
    +                this.person.load(id);
    +            }
    +        }
    +    }
    +}

With these pieces in place, we now have a functioning page that will display details about a person. We can start up the application and navigate to /Person/Details?id=1 (assuming a person with ID 1 exists - if not, navigate to /Person/Table and create one).

From this point, one can start adding more fields, more features, and more flair to the page. Check out all the other documentation in the sidebar to see what else Coalesce has to offer, including the Knockout Overview.

`,17),p=[e];function t(c,r,i,D,y,d){return a(),n("div",null,p)}const g=s(o,[["render",t]]);export{u as __pageData,g as default}; diff --git a/assets/stacks_ko_getting-started.md.KI5cX2XQ.lean.js b/assets/stacks_ko_getting-started.md.KI5cX2XQ.lean.js new file mode 100644 index 000000000..2af74d424 --- /dev/null +++ b/assets/stacks_ko_getting-started.md.KI5cX2XQ.lean.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as n,R as l}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"Getting Started with Knockout","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/getting-started.md","filePath":"stacks/ko/getting-started.md"}'),o={name:"stacks/ko/getting-started.md"},e=l("",17),p=[e];function t(c,r,i,D,y,d){return a(),n("div",null,p)}const g=s(o,[["render",t]]);export{u as __pageData,g as default}; diff --git a/assets/stacks_ko_overview.md.rAQfoqex.js b/assets/stacks_ko_overview.md.rAQfoqex.js new file mode 100644 index 000000000..809ed0b22 --- /dev/null +++ b/assets/stacks_ko_overview.md.rAQfoqex.js @@ -0,0 +1 @@ +import{_ as e,o,c as t,R as a}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"Knockout Overview","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/overview.md","filePath":"stacks/ko/overview.md"}'),i={name:"stacks/ko/overview.md"},r=a('

Knockout Overview

The Knockout stack for Coalesce offers the ability to build pages with the time-tested Knockout JavaScript library using all of the features of the Coalesce generated APIs and ViewModels. It can be used for anything between adding simple interactive augmentations of MVC pages to building a full MPA-SPA hybrid application.

Getting Started

Check out Getting Started with Knockout if you haven't already to learn how to get a new Coalesce Knockout project up and running.

Generated Code

Below you will find a brief overview of each of the different pieces of code that Coalesce will generate for you when you choose the Knockout stack.

TypeScript

Coalesce generates a number of different types of TypeScript classes to support your data through the generated API.

ViewModels

One view model class is generated for each of your Entity Models and Custom DTOs. These models contain fields for your model Properties, and functions and other members for your model Methods. They also contain a number of standard fields & functions inherited from BaseViewModel which offer basic loading & saving functionality, as well as other handy utility members for use with Knockout.

See TypeScript ViewModels for more details.

List ViewModels

One ListViewModel is generated for each of your Entity Models and Custom DTOs. These classes contain functionality for loading sets of objects from the server. They provide searching, paging, sorting, and filtering functionality.

See TypeScript ListViewModels for more details.

External Type ViewModels

Any non-primitive types which are not themselves a Entity Models or Custom DTOs which are accessible through the aforementioned types, either through one of its Properties, or return value from one of its Methods, will have a corresponding TypeScript ViewModel generated for it. These ViewModels only provide a KnockoutObservable field for each property on the C# class.

See TypeScript External ViewModels for more details.

View Controllers

For each of your Entity Models and Custom DTOs, a controller is created in the /Controllers/Generated directory of your web project. These controllers provide routes for the generated admin views.

As you add your own pages to your application, you should add additional partial classes in the /Controllers that extend these generated partial classes to expose those pages.

Admin Views

For each of your Entity Models and Custom DTOs, a number of views are generated to provide administrative-level access to your data.

Table

Provides a basic table view with sorting, searching, and paging of your data. Can be rendered in either read-only mode (routed as /Table), or editable mode (routed as TableEdit).

Cards

Provides a card-based view of your data with searching and paging.

CreateEdit

Provides an editor view which can be used to create new entities or edit existing ones.

EditorHtml

Provides a minimal amount of HTML to display an editor for the object type. This is used by the showEditor method on the generated TypeScript ViewModels.

',30),s=[r];function l(d,n,c,h,m,p){return o(),t("div",null,s)}const w=e(i,[["render",l]]);export{u as __pageData,w as default}; diff --git a/assets/stacks_ko_overview.md.rAQfoqex.lean.js b/assets/stacks_ko_overview.md.rAQfoqex.lean.js new file mode 100644 index 000000000..126d38b25 --- /dev/null +++ b/assets/stacks_ko_overview.md.rAQfoqex.lean.js @@ -0,0 +1 @@ +import{_ as e,o,c as t,R as a}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"Knockout Overview","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/ko/overview.md","filePath":"stacks/ko/overview.md"}'),i={name:"stacks/ko/overview.md"},r=a("",30),s=[r];function l(d,n,c,h,m,p){return o(),t("div",null,s)}const w=e(i,[["render",l]]);export{u as __pageData,w as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-audit-log-page.md.GDNXyXjl.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-audit-log-page.md.GDNXyXjl.js new file mode 100644 index 000000000..53d38e96b --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-audit-log-page.md.GDNXyXjl.js @@ -0,0 +1,14 @@ +import{_ as n,D as t,o as l,c as p,I as o,R as c,k as a,a as e}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-admin-audit-log-page","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.md"}'),i={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.md"},r=c(`

c-admin-audit-log-page

A full-featured page for interacting with Coalesce's Audit Logging. Presents a view similar to c-admin-table-page with content optimized for viewing audit log records. Designed to be routed to directly with vue-router.

Examples

ts
import { CAdminAuditLogPage } from 'coalesce-vue-vuetify3';
+const router = new Router({
+  // ...
+  routes: [
+    // ... other routes
+    {
+      path: '/admin/audit-logs',
+      component: CAdminAuditLogPage,
+      props: {
+        type: 'AuditLog'
+      }
+    },
+  ]
+})

Props

`,5),d=a("p",null,[e("The PascalCase name of your "),a("code",null,"IAuditLog"),e(" implementation.")],-1),u=a("p",null,[e("An optional "),a("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),e(" that will be used if provided instead of the one the component will create automatically from the provided "),a("code",null,"type"),e(" prop.")],-1),h=a("p",null,"A Vuetify color name to be applied to the toolbar at the top of the page.",-1),D=a("h2",{id:"slots",tabindex:"-1"},[e("Slots "),a("a",{class:"header-anchor",href:"#slots","aria-label":'Permalink to "Slots"'},"​")],-1),m=a("p",null,"A slot that can be used to replace the entire content of the Detail column on the page.",-1),g=a("p",null,"A slot that can be used to append additional content to the Detail column on the page.",-1);function y(_,f,C,v,A,b){const s=t("Prop");return l(),p("div",null,[r,o(s,{def:"type: string",lang:"ts"}),d,o(s,{def:"list?: ListViewModel",lang:"ts"}),u,o(s,{def:"color: string = 'primary'",lang:"ts"}),h,D,o(s,{def:"row-detail: { item: AuditLogViewModel }",lang:"ts"}),m,o(s,{def:"row-detail-append: { item: AuditLogViewModel }",lang:"ts"}),g])}const w=n(i,[["render",y]]);export{k as __pageData,w as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-audit-log-page.md.GDNXyXjl.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-audit-log-page.md.GDNXyXjl.lean.js new file mode 100644 index 000000000..3ef6cc40a --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-audit-log-page.md.GDNXyXjl.lean.js @@ -0,0 +1 @@ +import{_ as n,D as t,o as l,c as p,I as o,R as c,k as a,a as e}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-admin-audit-log-page","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.md"}'),i={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.md"},r=c("",5),d=a("p",null,[e("The PascalCase name of your "),a("code",null,"IAuditLog"),e(" implementation.")],-1),u=a("p",null,[e("An optional "),a("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),e(" that will be used if provided instead of the one the component will create automatically from the provided "),a("code",null,"type"),e(" prop.")],-1),h=a("p",null,"A Vuetify color name to be applied to the toolbar at the top of the page.",-1),D=a("h2",{id:"slots",tabindex:"-1"},[e("Slots "),a("a",{class:"header-anchor",href:"#slots","aria-label":'Permalink to "Slots"'},"​")],-1),m=a("p",null,"A slot that can be used to replace the entire content of the Detail column on the page.",-1),g=a("p",null,"A slot that can be used to append additional content to the Detail column on the page.",-1);function y(_,f,C,v,A,b){const s=t("Prop");return l(),p("div",null,[r,o(s,{def:"type: string",lang:"ts"}),d,o(s,{def:"list?: ListViewModel",lang:"ts"}),u,o(s,{def:"color: string = 'primary'",lang:"ts"}),h,D,o(s,{def:"row-detail: { item: AuditLogViewModel }",lang:"ts"}),m,o(s,{def:"row-detail-append: { item: AuditLogViewModel }",lang:"ts"}),g])}const w=n(i,[["render",y]]);export{k as __pageData,w as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-display.md.w8gdv3pP.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-display.md.w8gdv3pP.js new file mode 100644 index 000000000..2fd7c004b --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-display.md.w8gdv3pP.js @@ -0,0 +1,8 @@ +import{_ as e,o as a,c as s,R as o}from"./chunks/framework.g9eZ-ZSs.js";const D=JSON.parse('{"title":"c-admin-display","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-display.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-display.md"}'),t={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-display.md"},n=o(`

c-admin-display

Behaves the same as c-display, except any collection navigation properties will be rendered as links to an admin list page, and any models will be rendered as a link to an admin item page.

Links for collections are resolved from vue-router with a route name of coalesce-admin-list, a type route param containing the name of the collection's type, and a query parameter filter.<foreign key name> with a value of the primary key of the owner of the collection. This route is expected to resolve to a c-admin-table-page, which is setup by default by the template outlined in Getting Started with Vue.

Links for single models are resolved from vue-router with a route name of coalesce-admin-item, a type route param containing the name of the model's type, and a id route param containing the object's primary key. This route is expected to resolve to a c-admin-editor-page, which is setup by default by the template outlined in Getting Started with Vue.

Examples

template
<!-- Renders regularly as text: -->
+<c-admin-display :model="person" for="firstName" />
+
+<!-- Renders as a link to an item: -->
+<c-admin-display :model="person" for="company" />
+
+<!-- Renders as a link to a list: -->
+<c-admin-display :model="person" for="casesAssigned" />

Props

Same as c-display.

Slots

Same as c-display.

`,10),l=[n];function p(c,r,i,d,y,m){return a(),s("div",null,l)}const h=e(t,[["render",p]]);export{D as __pageData,h as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-display.md.w8gdv3pP.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-display.md.w8gdv3pP.lean.js new file mode 100644 index 000000000..2270f84e3 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-display.md.w8gdv3pP.lean.js @@ -0,0 +1 @@ +import{_ as e,o as a,c as s,R as o}from"./chunks/framework.g9eZ-ZSs.js";const D=JSON.parse('{"title":"c-admin-display","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-display.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-display.md"}'),t={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-display.md"},n=o("",10),l=[n];function p(c,r,i,d,y,m){return a(),s("div",null,l)}const h=e(t,[["render",p]]);export{D as __pageData,h as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor-page.md.SUSnocBA.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor-page.md.SUSnocBA.js new file mode 100644 index 000000000..3a0462d6f --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor-page.md.SUSnocBA.js @@ -0,0 +1,19 @@ +import{_ as o,D as t,o as l,c as p,I as e,R as c,k as s,a as n}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse('{"title":"c-admin-editor-page","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor-page.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor-page.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor-page.md"},i=c(`

c-admin-editor-page

A page for a creating/editing single ViewModel instance. Provides a c-admin-editor and a c-admin-methods for the instance. Designed to be routed to directly with vue-router.

Examples

ts
// router.ts or main.ts
+
+// WITHOUT Vuetify A la carte:
+import { CAdminEditorPage } from 'coalesce-vue-vuetify';
+// WITH Vuetify A-la-carte:
+import { CAdminEditorPage } from 'coalesce-vue-vuetify/lib';
+
+const router = new Router({
+    // ...
+    routes: [
+        // ... other routes
+        {
+            path: '/admin/:type/edit/:id?',
+            name: 'coalesce-admin-item',
+            component: CAdminEditorPage,
+            props: true,
+        },
+    ]
+})

Props

`,5),d=s("p",null,"The PascalCase name of the type to be created/edited.",-1),D=s("p",null,[n("The primary key of the item being edited. If null or not provided, the page will be creating a new instance of the provided "),s("code",null,"type"),n(" instead.")],-1);function y(m,u,C,h,f,v){const a=t("Prop");return l(),p("div",null,[i,e(a,{def:"type: string",lang:"ts"}),d,e(a,{def:"id?: number | string",lang:"ts"}),D])}const E=o(r,[["render",y]]);export{g as __pageData,E as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor-page.md.SUSnocBA.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor-page.md.SUSnocBA.lean.js new file mode 100644 index 000000000..d67b430dd --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor-page.md.SUSnocBA.lean.js @@ -0,0 +1 @@ +import{_ as o,D as t,o as l,c as p,I as e,R as c,k as s,a as n}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse('{"title":"c-admin-editor-page","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor-page.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor-page.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor-page.md"},i=c("",5),d=s("p",null,"The PascalCase name of the type to be created/edited.",-1),D=s("p",null,[n("The primary key of the item being edited. If null or not provided, the page will be creating a new instance of the provided "),s("code",null,"type"),n(" instead.")],-1);function y(m,u,C,h,f,v){const a=t("Prop");return l(),p("div",null,[i,e(a,{def:"type: string",lang:"ts"}),d,e(a,{def:"id?: number | string",lang:"ts"}),D])}const E=o(r,[["render",y]]);export{g as __pageData,E as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor.md.oQcY14k_.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor.md.oQcY14k_.js new file mode 100644 index 000000000..c53b6baae --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor.md.oQcY14k_.js @@ -0,0 +1 @@ +import{_ as s,D as t,o as n,c as l,I as c,R as r,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-admin-editor","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.md"}'),i={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.md"},p=r('

c-admin-editor

An editor for a single ViewModel instance. Provides a c-input for each property of the model.

Does not automatically enable auto-save - if desired, this must be enabled by the implementor of this component.

Examples

template
<c-admin-editor :model="person" />

Props

',6),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ViewModel"),a(" to render an editor for.")],-1);function m(u,h,_,f,v,y){const o=t("Prop");return n(),l("div",null,[p,c(o,{def:"model: ViewModel | ListViewModel",lang:"ts"}),d])}const C=s(i,[["render",m]]);export{k as __pageData,C as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor.md.oQcY14k_.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor.md.oQcY14k_.lean.js new file mode 100644 index 000000000..2d2370038 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-editor.md.oQcY14k_.lean.js @@ -0,0 +1 @@ +import{_ as s,D as t,o as n,c as l,I as c,R as r,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-admin-editor","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.md"}'),i={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.md"},p=r("",6),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ViewModel"),a(" to render an editor for.")],-1);function m(u,h,_,f,v,y){const o=t("Prop");return n(),l("div",null,[p,c(o,{def:"model: ViewModel | ListViewModel",lang:"ts"}),d])}const C=s(i,[["render",m]]);export{k as __pageData,C as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-method.md.b9u2lyQx.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-method.md.b9u2lyQx.js new file mode 100644 index 000000000..bf87df87e --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-method.md.b9u2lyQx.js @@ -0,0 +1 @@ +import{_ as s,D as n,o as l,c,I as t,R as r,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const b=JSON.parse('{"title":"c-admin-method","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-method.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-method.md"}'),d={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-method.md"},i=r('

c-admin-method

Provides an interface for invoking a method and rendering its result, designed to be use in an admin page.

For each parameter of a method, a c-input will be rendered to accept the input of that parameter. A button is provided to trigger an invocation of the method, progress and errors are rendered with a c-loader-status, and results are rendered with c-display.

Examples

template
<c-admin-method :model="person" for="setTitle" auto-reload-model />

Props

',6),p=e("p",null,"A metadata specifier for the method. One of:",-1),h=e("ul",null,[e("li",null,[a("A string with the name of the method belonging to "),e("code",null,"model"),a(".")]),e("li",null,"A direct reference to a method's metadata object."),e("li",null,"A string in dot-notation that starts with a type name and ending with a method name.")],-1),m=e("p",null,[a("An "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ViewModel"),a(" or "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" owning the method and "),e("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#api-callers"},"API Caller"),a(" that was specified by the "),e("code",null,"for"),a(" prop.")],-1),u=e("p",null,[a("True if the "),e("code",null,"model"),a(" should have its "),e("code",null,"$load"),a(" invoked after a successful invocation of the method.")],-1);function f(v,_,y,D,g,C){const o=n("Prop");return l(),c("div",null,[i,t(o,{def:"for: string | Method",lang:"ts"}),p,h,t(o,{def:"model: ViewModel | ListViewModel",lang:"ts"}),m,t(o,{def:"autoReloadModel?: boolean = false",lang:"ts"}),u])}const w=s(d,[["render",f]]);export{b as __pageData,w as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-method.md.b9u2lyQx.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-method.md.b9u2lyQx.lean.js new file mode 100644 index 000000000..11ef82d8e --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-method.md.b9u2lyQx.lean.js @@ -0,0 +1 @@ +import{_ as s,D as n,o as l,c,I as t,R as r,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const b=JSON.parse('{"title":"c-admin-method","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-method.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-method.md"}'),d={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-method.md"},i=r("",6),p=e("p",null,"A metadata specifier for the method. One of:",-1),h=e("ul",null,[e("li",null,[a("A string with the name of the method belonging to "),e("code",null,"model"),a(".")]),e("li",null,"A direct reference to a method's metadata object."),e("li",null,"A string in dot-notation that starts with a type name and ending with a method name.")],-1),m=e("p",null,[a("An "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ViewModel"),a(" or "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" owning the method and "),e("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#api-callers"},"API Caller"),a(" that was specified by the "),e("code",null,"for"),a(" prop.")],-1),u=e("p",null,[a("True if the "),e("code",null,"model"),a(" should have its "),e("code",null,"$load"),a(" invoked after a successful invocation of the method.")],-1);function f(v,_,y,D,g,C){const o=n("Prop");return l(),c("div",null,[i,t(o,{def:"for: string | Method",lang:"ts"}),p,h,t(o,{def:"model: ViewModel | ListViewModel",lang:"ts"}),m,t(o,{def:"autoReloadModel?: boolean = false",lang:"ts"}),u])}const w=s(d,[["render",f]]);export{b as __pageData,w as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-methods.md.roY2qkqn.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-methods.md.roY2qkqn.js new file mode 100644 index 000000000..1be2034d3 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-methods.md.roY2qkqn.js @@ -0,0 +1 @@ +import{_ as l,D as t,o as n,c as p,I as o,R as c,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-admin-methods","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-methods.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-methods.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-methods.md"},d=c('

c-admin-methods

Renders in a Vuetify v-expansion-panels a c-admin-method for each method on a ViewModel or ListViewModel.

Examples

template
<c-admin-methods :model="person" class="x" auto-reload-model />
template
<c-admin-methods :model="person" auto-reload-model />
template
<c-admin-methods :model="personList" auto-reload-model />

Props

',7),i=e("p",null,[s("An "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ViewModel"),s(" or "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),s(" whose methods should each render as a "),e("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-admin-method.html"},"c-admin-method"),s(".")],-1),m=e("p",null,[s("True if the "),e("code",null,"model"),s(" should have its "),e("code",null,"$load"),s(" invoked after a successful invocation of any method.")],-1);function h(u,D,y,v,C,f){const a=t("Prop");return n(),p("div",null,[d,o(a,{def:"model: ViewModel | ListViewModel",lang:"ts"}),i,o(a,{def:"autoReloadModel?: boolean = false",lang:"ts"}),m])}const g=l(r,[["render",h]]);export{k as __pageData,g as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-methods.md.roY2qkqn.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-methods.md.roY2qkqn.lean.js new file mode 100644 index 000000000..a3762e35f --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-methods.md.roY2qkqn.lean.js @@ -0,0 +1 @@ +import{_ as l,D as t,o as n,c as p,I as o,R as c,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-admin-methods","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-methods.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-methods.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-methods.md"},d=c("",7),i=e("p",null,[s("An "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ViewModel"),s(" or "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),s(" whose methods should each render as a "),e("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-admin-method.html"},"c-admin-method"),s(".")],-1),m=e("p",null,[s("True if the "),e("code",null,"model"),s(" should have its "),e("code",null,"$load"),s(" invoked after a successful invocation of any method.")],-1);function h(u,D,y,v,C,f){const a=t("Prop");return n(),p("div",null,[d,o(a,{def:"model: ViewModel | ListViewModel",lang:"ts"}),i,o(a,{def:"autoReloadModel?: boolean = false",lang:"ts"}),m])}const g=l(r,[["render",h]]);export{k as __pageData,g as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-page.md.a48uAuy8.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-page.md.a48uAuy8.js new file mode 100644 index 000000000..e83a734dd --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-page.md.a48uAuy8.js @@ -0,0 +1,19 @@ +import{_ as l,D as o,o as t,c as p,I as e,R as c,k as s,a as n}from"./chunks/framework.g9eZ-ZSs.js";const b=JSON.parse('{"title":"c-admin-table-page","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.md"},i=c(`

c-admin-table-page

A full-featured page for interacting with a ListViewModel. Provides a c-admin-table and a c-admin-methods for the list. Designed to be routed to directly with vue-router.

Examples

ts
// router.ts or main.ts
+
+// WITHOUT Vuetify A la carte:
+import { CAdminTablePage } from 'coalesce-vue-vuetify';
+// WITH Vuetify A-la-carte:
+import { CAdminTablePage } from 'coalesce-vue-vuetify/lib';
+
+const router = new Router({
+    // ...
+    routes: [
+        // ... other routes
+        {
+            path: '/admin/:type',
+            name: 'coalesce-admin-list',
+            component: CAdminTablePage,
+            props: true,
+        },
+    ]
+})

Props

`,5),d=s("p",null,"The PascalCase name of the type to be listed.",-1),D=s("p",null,[n("An optional "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),n(" that will be used if provided instead of the one the component will otherwise create automatically.")],-1);function y(m,u,C,h,f,v){const a=o("Prop");return t(),p("div",null,[i,e(a,{def:"type: string",lang:"ts"}),d,e(a,{def:"list?: ListViewModel",lang:"ts"}),D])}const g=l(r,[["render",y]]);export{b as __pageData,g as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-page.md.a48uAuy8.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-page.md.a48uAuy8.lean.js new file mode 100644 index 000000000..22b1728b3 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-page.md.a48uAuy8.lean.js @@ -0,0 +1 @@ +import{_ as l,D as o,o as t,c as p,I as e,R as c,k as s,a as n}from"./chunks/framework.g9eZ-ZSs.js";const b=JSON.parse('{"title":"c-admin-table-page","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.md"},i=c("",5),d=s("p",null,"The PascalCase name of the type to be listed.",-1),D=s("p",null,[n("An optional "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),n(" that will be used if provided instead of the one the component will otherwise create automatically.")],-1);function y(m,u,C,h,f,v){const a=o("Prop");return t(),p("div",null,[i,e(a,{def:"type: string",lang:"ts"}),d,e(a,{def:"list?: ListViewModel",lang:"ts"}),D])}const g=l(r,[["render",y]]);export{b as __pageData,g as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-toolbar.md.hkVtE9hX.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-toolbar.md.hkVtE9hX.js new file mode 100644 index 000000000..612e8327a --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-toolbar.md.hkVtE9hX.js @@ -0,0 +1 @@ +import{_ as t,D as l,o as n,c,I as o,R as p,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse('{"title":"c-admin-table-toolbar","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-toolbar.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-toolbar.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-toolbar.md"},i=p('

c-admin-table-toolbar

A full-featured toolbar for a ListViewModel designed to be used on an admin page, including "Create" and "Reload" buttons, a c-list-range-display, a c-list-page, a search field, c-list-filters, and a c-list-page-size.

Examples

template
<c-admin-table-toolbar :list="personList" />
template
<c-admin-table-toolbar :list="personList" color="pink" :editable.sync="isEditable" />

Props

',6),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" to render the toolbar for.")],-1),u=e("p",null,[a("The "),e("a",{href:"https://vuetifyjs.com/en/styles/colors/",target:"_blank",rel:"noreferrer"},"color"),a(" of the toolbar.")],-1),D=e("p",null,[a("If provided, adds a button to toggle the value of this prop. Should be bound with the "),e("code",null,".sync"),a(" modifier.")],-1);function m(y,h,b,f,v,_){const s=l("Prop");return n(),c("div",null,[i,o(s,{def:"list: ListViewModel",lang:"ts"}),d,o(s,{def:"color: string = 'primary'",lang:"ts"}),u,o(s,{def:"editable?: boolean",lang:"ts"}),D])}const k=t(r,[["render",m]]);export{g as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-toolbar.md.hkVtE9hX.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-toolbar.md.hkVtE9hX.lean.js new file mode 100644 index 000000000..a7b0671bc --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table-toolbar.md.hkVtE9hX.lean.js @@ -0,0 +1 @@ +import{_ as t,D as l,o as n,c,I as o,R as p,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const g=JSON.parse('{"title":"c-admin-table-toolbar","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-toolbar.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-toolbar.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-table-toolbar.md"},i=p("",6),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" to render the toolbar for.")],-1),u=e("p",null,[a("The "),e("a",{href:"https://vuetifyjs.com/en/styles/colors/",target:"_blank",rel:"noreferrer"},"color"),a(" of the toolbar.")],-1),D=e("p",null,[a("If provided, adds a button to toggle the value of this prop. Should be bound with the "),e("code",null,".sync"),a(" modifier.")],-1);function m(y,h,b,f,v,_){const s=l("Prop");return n(),c("div",null,[i,o(s,{def:"list: ListViewModel",lang:"ts"}),d,o(s,{def:"color: string = 'primary'",lang:"ts"}),u,o(s,{def:"editable?: boolean",lang:"ts"}),D])}const k=t(r,[["render",m]]);export{g as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table.md.PTEjWSuO.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table.md.PTEjWSuO.js new file mode 100644 index 000000000..92a9ff5ab --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table.md.PTEjWSuO.js @@ -0,0 +1 @@ +import{_ as o,D as l,o as n,c,I as s,R as i,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-admin-table","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-table.md"},d=i('

c-admin-table

An full-featured table for a ListViewModel, including a c-admin-table-toolbar, c-table, and c-list-pagination.

The table can be in read mode (default), or toggled into edit mode with the button provided by the c-admin-table-toolbar. When placed into edit mode, auto-save is enabled.

Examples

template
<c-admin-table :list="personList" />

Props

',6),p=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" to render a table for.")],-1),m=e("p",null,[a("An optional list of available page sizes to offer through the "),e("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.html"},"c-list-pagination"),a("'s "),e("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.html"},"c-list-page-size"),a(" component. Defaults to "),e("code",null,"[10, 25, 100]"),a(".")],-1),u=e("p",null,[a("If true, the "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Data Source Standard Parameters"),a(" of the provided "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(` will be read from and written to the window's query string. The "Editable" state of the table will also be bound to the query string.`)],-1);function h(f,v,b,_,y,g){const t=l("Prop");return n(),c("div",null,[d,s(t,{def:"list: ListViewModel",lang:"ts"}),p,s(t,{def:"pageSizes?: number[]",lang:"ts"}),m,s(t,{def:"queryBind?: boolean",lang:"ts"}),u])}const C=o(r,[["render",h]]);export{k as __pageData,C as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table.md.PTEjWSuO.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table.md.PTEjWSuO.lean.js new file mode 100644 index 000000000..f72f2850f --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-admin-table.md.PTEjWSuO.lean.js @@ -0,0 +1 @@ +import{_ as o,D as l,o as n,c,I as s,R as i,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-admin-table","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-admin-table.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-admin-table.md"},d=i("",6),p=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" to render a table for.")],-1),m=e("p",null,[a("An optional list of available page sizes to offer through the "),e("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.html"},"c-list-pagination"),a("'s "),e("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.html"},"c-list-page-size"),a(" component. Defaults to "),e("code",null,"[10, 25, 100]"),a(".")],-1),u=e("p",null,[a("If true, the "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Data Source Standard Parameters"),a(" of the provided "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(` will be read from and written to the window's query string. The "Editable" state of the table will also be bound to the query string.`)],-1);function h(f,v,b,_,y,g){const t=l("Prop");return n(),c("div",null,[d,s(t,{def:"list: ListViewModel",lang:"ts"}),p,s(t,{def:"pageSizes?: number[]",lang:"ts"}),m,s(t,{def:"queryBind?: boolean",lang:"ts"}),u])}const C=o(r,[["render",h]]);export{k as __pageData,C as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-datetime-picker.md.z5FrZ602.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-datetime-picker.md.z5FrZ602.js new file mode 100644 index 000000000..7beef72d8 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-datetime-picker.md.z5FrZ602.js @@ -0,0 +1,10 @@ +import{_ as n,D as l,o as i,c,I as s,R as o,k as e,a as t}from"./chunks/framework.g9eZ-ZSs.js";const A=JSON.parse('{"title":"c-datetime-picker","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-datetime-picker.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-datetime-picker.md"}'),p={name:"stacks/vue/coalesce-vue-vuetify/components/c-datetime-picker.md"},d=o(`

c-datetime-picker

A general, all-purpose date/time input component that can be used either with models and metadata or as a standalone component using only v-model.

Examples

template
<c-datetime-picker :model="person" for="birthDate" />
+
+<c-datetime-picker v-model="standaloneDate" />
+
+<c-datetime-picker 
+    v-model="standaloneTime" 
+    date-kind="time"
+    date-format="h:mm a"
+/>

Props

`,5),r=e("p",null,"A metadata specifier for the value being bound. One of:",-1),u=e("ul",null,[e("li",null,[t("A string with the name of the value belonging to "),e("code",null,"model"),t(".")]),e("li",null,"A direct reference to a metadata object."),e("li",null,"A string in dot-notation that starts with a type name.")],-1),h=e("p",null,[t("An object owning the value that was specified by the "),e("code",null,"for"),t(" prop. If provided, the input will be bound to the corresponding property on the "),e("code",null,"model"),t(" object.")],-1),m=e("p",null,[t("If binding the component with "),e("code",null,"v-model"),t(", accepts the "),e("code",null,"value"),t(" part of "),e("code",null,"v-model"),t(".")],-1),D=e("p",null,[t("Whether the date is only a date, only a time, or contains significant date "),e("code",null,"and"),t(" time information.")],-1),f=e("p",null,[t("If the component was bound with metadata using the "),e("code",null,"for"),t(" prop, this will default to the kind specified by "),e("a",{href:"/Coalesce/modeling/model-components/attributes/date-type.html"},"[DateType]"),t(".")],-1),y=o('

The format of the date that will be rendered in the component's text field, and the format that will be attempted first when parsing user input in the text field.

Defaults to:

  • M/d/yyyy h:mm a if dateKind == 'datetime',
  • M/d/yyyy if dateKind == 'date', or
  • h:mm a if dateKind == 'time'.

WARNING

When parsing a user's text input into the text field, c-datetime-picker will first attempt to parse it with the format specified by dateFormat, or the default as described above if not explicitly specified.

If this fails, the date will be parsed with the Date constructor, but only if the dateKind is datetime or date. This works fairly well on all modern browsers, but can still occasionally have issues. c-datetime-picker tries its best to filter out bad parses from the Date constructor, like dates with a year earlier than 1000.

',4),_=e("p",null,"True if a native HTML5 input should be used instead of a popup menu with Vuetify date/time pickers inside of it.",-1),b=e("p",null,"True if the calendar and clock should be shown side by side in the picker menu, rather than in separate tabs.",-1),k=e("p",null,"True if the component should be read-only.",-1),g=e("p",null,"True if the component should be disabled.",-1);function v(C,w,T,q,E,x){const a=l("Prop");return i(),c("div",null,[d,s(a,{def:"for?: string | DateProperty | DateValue",lang:"ts"}),r,u,s(a,{def:"model?: Model | DataSource",lang:"ts"}),h,s(a,{def:`value?: Date // Vue 2 +modelValue?: Date // Vue 3`,lang:"ts"}),m,s(a,{def:"dateKind?: 'date' | 'time' | 'datetime' = 'datetime'",lang:"ts"}),D,f,s(a,{def:"dateFormat?: string",lang:"ts"}),y,s(a,{def:"native?: boolean",lang:"ts"}),_,s(a,{def:"sideBySide?: boolean",lang:"ts"}),b,s(a,{def:"readonly?: boolean",lang:"ts"}),k,s(a,{def:"disabled?: boolean",lang:"ts"}),g])}const V=n(p,[["render",v]]);export{A as __pageData,V as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-datetime-picker.md.z5FrZ602.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-datetime-picker.md.z5FrZ602.lean.js new file mode 100644 index 000000000..658bf6498 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-datetime-picker.md.z5FrZ602.lean.js @@ -0,0 +1,2 @@ +import{_ as n,D as l,o as i,c,I as s,R as o,k as e,a as t}from"./chunks/framework.g9eZ-ZSs.js";const A=JSON.parse('{"title":"c-datetime-picker","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-datetime-picker.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-datetime-picker.md"}'),p={name:"stacks/vue/coalesce-vue-vuetify/components/c-datetime-picker.md"},d=o("",5),r=e("p",null,"A metadata specifier for the value being bound. One of:",-1),u=e("ul",null,[e("li",null,[t("A string with the name of the value belonging to "),e("code",null,"model"),t(".")]),e("li",null,"A direct reference to a metadata object."),e("li",null,"A string in dot-notation that starts with a type name.")],-1),h=e("p",null,[t("An object owning the value that was specified by the "),e("code",null,"for"),t(" prop. If provided, the input will be bound to the corresponding property on the "),e("code",null,"model"),t(" object.")],-1),m=e("p",null,[t("If binding the component with "),e("code",null,"v-model"),t(", accepts the "),e("code",null,"value"),t(" part of "),e("code",null,"v-model"),t(".")],-1),D=e("p",null,[t("Whether the date is only a date, only a time, or contains significant date "),e("code",null,"and"),t(" time information.")],-1),f=e("p",null,[t("If the component was bound with metadata using the "),e("code",null,"for"),t(" prop, this will default to the kind specified by "),e("a",{href:"/Coalesce/modeling/model-components/attributes/date-type.html"},"[DateType]"),t(".")],-1),y=o("",4),_=e("p",null,"True if a native HTML5 input should be used instead of a popup menu with Vuetify date/time pickers inside of it.",-1),b=e("p",null,"True if the calendar and clock should be shown side by side in the picker menu, rather than in separate tabs.",-1),k=e("p",null,"True if the component should be read-only.",-1),g=e("p",null,"True if the component should be disabled.",-1);function v(C,w,T,q,E,x){const a=l("Prop");return i(),c("div",null,[d,s(a,{def:"for?: string | DateProperty | DateValue",lang:"ts"}),r,u,s(a,{def:"model?: Model | DataSource",lang:"ts"}),h,s(a,{def:`value?: Date // Vue 2 +modelValue?: Date // Vue 3`,lang:"ts"}),m,s(a,{def:"dateKind?: 'date' | 'time' | 'datetime' = 'datetime'",lang:"ts"}),D,f,s(a,{def:"dateFormat?: string",lang:"ts"}),y,s(a,{def:"native?: boolean",lang:"ts"}),_,s(a,{def:"sideBySide?: boolean",lang:"ts"}),b,s(a,{def:"readonly?: boolean",lang:"ts"}),k,s(a,{def:"disabled?: boolean",lang:"ts"}),g])}const V=n(p,[["render",v]]);export{A as __pageData,V as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-display.md.BR23Vc5-.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-display.md.BR23Vc5-.js new file mode 100644 index 000000000..b44c98369 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-display.md.BR23Vc5-.js @@ -0,0 +1,6 @@ +import{_ as l,D as n,o as p,c as r,I as o,R as t,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const E=JSON.parse('{"title":"c-display","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-display.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-display.md"}'),c={name:"stacks/vue/coalesce-vue-vuetify/components/c-display.md"},i=t(`

c-display

A general-purpose component for displaying any Value by rendering the value to a string with the display functions from the Models Layer. For plain string and number values, usage of this component is largely superfluous. For all other value types including dates, booleans, enums, objects, and collections, it is very handy.

Examples

Typical usage, providing an object and a property on that object:

template
<c-display :model="person" for="gender" />

Customizing date formatting (view format patterns):

template
<c-display :model="person" for="birthDate" format="M/d/yyyy" />

A contrived example of using c-display to render the result of an API Caller:

template
<c-display 
+    :value="person.setFirstName.result" 
+    :for="person.$metadata.methods.setFirstName.return" 
+    element="div"
+/>

Displaying a standalone date value without a model or other source of metadata:

template
<c-display :value="dateProp" format="M/d/yyyy" />

Props

`,12),d=e("p",null,[a("A metadata specifier for the value being bound. Either a direct reference to the metadata object, or a string with the name of the value belonging to "),e("code",null,"model"),a(", or a string in dot-notation that starts with a type name.")],-1),D=e("p",null,[a("An object owning the value that was specified by the "),e("code",null,"for"),a(" prop.")],-1),y=e("p",null,[a("Shorthand for "),e("code",null,':options="{ format: format }"'),a(", allowing for specification of the format to be used when displaying dates.")],-1),u=e("p",null,[a("See "),e("a",{href:"/Coalesce/stacks/vue/layers/models.html#displayoptions"},"DisplayOptions"),a(" for details on the options available for "),e("code",null,"format"),a(".")],-1),h=e("p",null,[a("Specify options for formatting some kinds of values, including dates. See "),e("a",{href:"/Coalesce/stacks/vue/layers/models.html#displayoptions"},"DisplayOptions"),a(" for details.")],-1),m=t('

Can be provided the value to be displayed in conjunction with the for prop, as an alternative to the model prop.

This is an uncommon scenario - it is generally easier to use the for/model props together.

Slots

default - Used to display fallback content if the value being displayed is either null or "" (empty string).

[DataTypeAttribute]

For properties and other values annotated with [DataTypeAttribute], the following special handling occurs based on the data type:

  • DataType.MultilineText: Renders with white-space: pre-wrap.
  • DataType.Password: Renders with a show/hide toggle (hidden by default), showing a fixed number of dot characters when hidden.
  • DataType.Url: Renders as a clickable link.
  • DataType.EmailAddress: Renders as a clickable mailto link.
  • DataType.PhoneNumber: Renders as a clickable tel link.
  • DataType.ImageUrl: Renders as an img element.
  • "Color": Renders a colored dot next to the value, interpreting the field value as a 7-character HTML hex color code.
',7);function f(C,g,v,_,b,k){const s=n("Prop");return p(),r("div",null,[i,o(s,{def:"for: string | Property | Value",lang:"ts"}),d,o(s,{def:"model?: Model | DataSource",lang:"ts"}),D,o(s,{def:'format: DisplayOptions["format"]',lang:"ts"}),y,u,o(s,{def:"options: DisplayOptions",lang:"ts"}),h,o(s,{def:`value: any // Vue 2 +modelValue: any // Vue 3`,lang:"ts"}),m])}const F=l(c,[["render",f]]);export{E as __pageData,F as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-display.md.BR23Vc5-.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-display.md.BR23Vc5-.lean.js new file mode 100644 index 000000000..f02869733 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-display.md.BR23Vc5-.lean.js @@ -0,0 +1,2 @@ +import{_ as l,D as n,o as p,c as r,I as o,R as t,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const E=JSON.parse('{"title":"c-display","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-display.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-display.md"}'),c={name:"stacks/vue/coalesce-vue-vuetify/components/c-display.md"},i=t("",12),d=e("p",null,[a("A metadata specifier for the value being bound. Either a direct reference to the metadata object, or a string with the name of the value belonging to "),e("code",null,"model"),a(", or a string in dot-notation that starts with a type name.")],-1),D=e("p",null,[a("An object owning the value that was specified by the "),e("code",null,"for"),a(" prop.")],-1),y=e("p",null,[a("Shorthand for "),e("code",null,':options="{ format: format }"'),a(", allowing for specification of the format to be used when displaying dates.")],-1),u=e("p",null,[a("See "),e("a",{href:"/Coalesce/stacks/vue/layers/models.html#displayoptions"},"DisplayOptions"),a(" for details on the options available for "),e("code",null,"format"),a(".")],-1),h=e("p",null,[a("Specify options for formatting some kinds of values, including dates. See "),e("a",{href:"/Coalesce/stacks/vue/layers/models.html#displayoptions"},"DisplayOptions"),a(" for details.")],-1),m=t("",7);function f(C,g,v,_,b,k){const s=n("Prop");return p(),r("div",null,[i,o(s,{def:"for: string | Property | Value",lang:"ts"}),d,o(s,{def:"model?: Model | DataSource",lang:"ts"}),D,o(s,{def:'format: DisplayOptions["format"]',lang:"ts"}),y,u,o(s,{def:"options: DisplayOptions",lang:"ts"}),h,o(s,{def:`value: any // Vue 2 +modelValue: any // Vue 3`,lang:"ts"}),m])}const F=l(c,[["render",f]]);export{E as __pageData,F as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-input.md.g15AvQ1z.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-input.md.g15AvQ1z.js new file mode 100644 index 000000000..e2f881d82 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-input.md.g15AvQ1z.js @@ -0,0 +1,6 @@ +import{_ as t,D as l,o as n,c as p,I as o,R as c,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const q=JSON.parse('{"title":"c-input","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-input.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-input.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-input.md"},i=c(`

c-input

A general-purpose input component for most Values. c-input does not have much functionality of its own - instead, it delegates to the right kind of component based on the type of value to which it is bound. This includes both other Coalesce Vuetify Components as well as direct usages of some Vuetify components.

All attributes are passed through to the delegated-to component, allowing for full customization of the underlying Vuetify component.

A summary of the components delegated to, by type:

Any other unsupported type will simply be displayed with c-display, unless a default slot is provided - in that case, the default slot will be rendered instead.

When bound to a ViewModel, the validation rules for the bound property will be obtained from the ViewModel and passed to Vuetify's rules prop.

Examples

Typical usage, providing an object and a property on that object:

template
<c-input :model="person" for="firstName" />

Customizing the Vuetify component used:

template
<c-input :model="comment" for="content" textarea solo />

Binding to API Caller args objects:

template
<c-input 
+    :model="person.setFirstName" 
+    for="newName" />

Or, using a more verbose syntax:

template
<c-input 
+    :model="person.setFirstName.args" 
+    for="Person.methods.setFirstName.newName" />

Binding to Data Source Parameters:

template
<c-input :model="personList.$dataSource" for="startsWith" />

Usage with v-model (this scenario is atypical - the model/for pair of props are used in almost all scenarios):

template
<c-input v-model="person.firstName" for="Person.firstName" />

Props

`,21),u=e("p",null,"A metadata specifier for the value being bound. One of:",-1),d=e("ul",null,[e("li",null,[s("A string with the name of the value belonging to "),e("code",null,"model"),s(".")]),e("li",null,"A direct reference to a metadata object."),e("li",null,"A string in dot-notation that starts with a type name.")],-1),y=e("p",null,[s("An object owning the value that was specified by the "),e("code",null,"for"),s(" prop. If provided, the input will be bound to the corresponding property on the "),e("code",null,"model"),s(" object.")],-1),D=e("p",null,[s("If binding the component with "),e("code",null,"v-model"),s(", accepts the "),e("code",null,"value"),s(" part of "),e("code",null,"v-model"),s(".")],-1),m=e("h2",{id:"slots",tabindex:"-1"},[s("Slots "),e("a",{class:"header-anchor",href:"#slots","aria-label":'Permalink to "Slots"'},"​")],-1),h=e("p",null,[e("code",null,"default"),s(" - Used to display fallback content if c-input does not support the type of the value being bound. Generally this does not need to be used, as you should avoid creating c-input components for unsupported types in the first place.")],-1);function f(v,C,g,b,k,_){const a=l("Prop");return n(),p("div",null,[i,o(a,{def:"for?: string | Property | Value",lang:"ts"}),u,d,o(a,{def:"model?: Model | DataSource",lang:"ts"}),y,o(a,{def:`value?: any // Vue 2 +modelValue?: any // Vue 3`,lang:"ts"}),D,m,h])}const F=t(r,[["render",f]]);export{q as __pageData,F as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-input.md.g15AvQ1z.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-input.md.g15AvQ1z.lean.js new file mode 100644 index 000000000..ac79efdf5 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-input.md.g15AvQ1z.lean.js @@ -0,0 +1,2 @@ +import{_ as t,D as l,o as n,c as p,I as o,R as c,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const q=JSON.parse('{"title":"c-input","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-input.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-input.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-input.md"},i=c("",21),u=e("p",null,"A metadata specifier for the value being bound. One of:",-1),d=e("ul",null,[e("li",null,[s("A string with the name of the value belonging to "),e("code",null,"model"),s(".")]),e("li",null,"A direct reference to a metadata object."),e("li",null,"A string in dot-notation that starts with a type name.")],-1),y=e("p",null,[s("An object owning the value that was specified by the "),e("code",null,"for"),s(" prop. If provided, the input will be bound to the corresponding property on the "),e("code",null,"model"),s(" object.")],-1),D=e("p",null,[s("If binding the component with "),e("code",null,"v-model"),s(", accepts the "),e("code",null,"value"),s(" part of "),e("code",null,"v-model"),s(".")],-1),m=e("h2",{id:"slots",tabindex:"-1"},[s("Slots "),e("a",{class:"header-anchor",href:"#slots","aria-label":'Permalink to "Slots"'},"​")],-1),h=e("p",null,[e("code",null,"default"),s(" - Used to display fallback content if c-input does not support the type of the value being bound. Generally this does not need to be used, as you should avoid creating c-input components for unsupported types in the first place.")],-1);function f(v,C,g,b,k,_){const a=l("Prop");return n(),p("div",null,[i,o(a,{def:"for?: string | Property | Value",lang:"ts"}),u,d,o(a,{def:"model?: Model | DataSource",lang:"ts"}),y,o(a,{def:`value?: any // Vue 2 +modelValue?: any // Vue 3`,lang:"ts"}),D,m,h])}const F=t(r,[["render",f]]);export{q as __pageData,F as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-filters.md.3ww2oFHW.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-filters.md.3ww2oFHW.js new file mode 100644 index 000000000..54867dcaa --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-filters.md.3ww2oFHW.js @@ -0,0 +1 @@ +import{_ as t,D as o,o as l,c as r,I as c,R as n,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-list-filters","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-filters.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-filters.md"}'),i={name:"stacks/vue/coalesce-vue-vuetify/components/c-list-filters.md"},p=n('

c-list-filters

A component that provides an interface for modifying the filters prop of a ListViewModel's parameters.

Example Usage

template
<c-list-filters :list="list" />

Props

',5),d=e("p",null,[s("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),s(" whose filters will be editable.")],-1);function m(f,h,u,_,D,v){const a=o("Prop");return l(),r("div",null,[p,c(a,{def:"list: ListViewModel",lang:"ts"}),d])}const C=t(i,[["render",m]]);export{k as __pageData,C as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-filters.md.3ww2oFHW.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-filters.md.3ww2oFHW.lean.js new file mode 100644 index 000000000..a88aa5fe7 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-filters.md.3ww2oFHW.lean.js @@ -0,0 +1 @@ +import{_ as t,D as o,o as l,c as r,I as c,R as n,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-list-filters","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-filters.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-filters.md"}'),i={name:"stacks/vue/coalesce-vue-vuetify/components/c-list-filters.md"},p=n("",5),d=e("p",null,[s("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),s(" whose filters will be editable.")],-1);function m(f,h,u,_,D,v){const a=o("Prop");return l(),r("div",null,[p,c(a,{def:"list: ListViewModel",lang:"ts"}),d])}const C=t(i,[["render",m]]);export{k as __pageData,C as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page-size.md.iQFFEkp9.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page-size.md.iQFFEkp9.js new file mode 100644 index 000000000..49c3bae71 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page-size.md.iQFFEkp9.js @@ -0,0 +1 @@ +import{_ as o,D as l,o as c,c as n,I as t,R as p,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const z=JSON.parse('{"title":"c-list-page-size","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.md"}'),i={name:"stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.md"},r=p('

c-list-page-size

A component that provides an dropdown for modifying the pageSize parameter prop of a ListViewModel.

Example Usage

template
<c-list-page-size :list="list" />

Props

',5),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" whose pagination will be editable.")],-1),u=e("p",null,[a("An optional list of available page sizes to offer through "),e("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.html"},"c-list-page-size"),a(". Defaults to "),e("code",null,"[10, 25, 100]"),a(".")],-1);function m(h,_,g,f,v,D){const s=l("Prop");return c(),n("div",null,[r,t(s,{def:"list: ListViewModel",lang:"ts"}),d,t(s,{def:"pageSizes?: number[]",lang:"ts"}),u])}const k=o(i,[["render",m]]);export{z as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page-size.md.iQFFEkp9.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page-size.md.iQFFEkp9.lean.js new file mode 100644 index 000000000..8e6316279 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page-size.md.iQFFEkp9.lean.js @@ -0,0 +1 @@ +import{_ as o,D as l,o as c,c as n,I as t,R as p,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const z=JSON.parse('{"title":"c-list-page-size","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.md"}'),i={name:"stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.md"},r=p("",5),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" whose pagination will be editable.")],-1),u=e("p",null,[a("An optional list of available page sizes to offer through "),e("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.html"},"c-list-page-size"),a(". Defaults to "),e("code",null,"[10, 25, 100]"),a(".")],-1);function m(h,_,g,f,v,D){const s=l("Prop");return c(),n("div",null,[r,t(s,{def:"list: ListViewModel",lang:"ts"}),d,t(s,{def:"pageSizes?: number[]",lang:"ts"}),u])}const k=o(i,[["render",m]]);export{z as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page.md.VoIz1Ye0.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page.md.VoIz1Ye0.js new file mode 100644 index 000000000..c6a90de04 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page.md.VoIz1Ye0.js @@ -0,0 +1 @@ +import{_ as t,D as o,o as l,c,I as n,R as p,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const y=JSON.parse('{"title":"c-list-page","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-page.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-page.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-list-page.md"},i=p('

c-list-page

A component that provides previous/next buttons and a text field for modifying the page parameter prop of a ListViewModel.

Example Usage

template
<c-list-page :list="list" />

Props

',5),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" whose current page will be changeable with the component.")],-1);function m(h,u,_,g,f,v){const s=o("Prop");return l(),c("div",null,[i,n(s,{def:"list: ListViewModel",lang:"ts"}),d])}const k=t(r,[["render",m]]);export{y as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page.md.VoIz1Ye0.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page.md.VoIz1Ye0.lean.js new file mode 100644 index 000000000..b55b768af --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-page.md.VoIz1Ye0.lean.js @@ -0,0 +1 @@ +import{_ as t,D as o,o as l,c,I as n,R as p,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const y=JSON.parse('{"title":"c-list-page","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-page.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-page.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-list-page.md"},i=p("",5),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" whose current page will be changeable with the component.")],-1);function m(h,u,_,g,f,v){const s=o("Prop");return l(),c("div",null,[i,n(s,{def:"list: ListViewModel",lang:"ts"}),d])}const k=t(r,[["render",m]]);export{y as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-pagination.md.dKBYttYP.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-pagination.md.dKBYttYP.js new file mode 100644 index 000000000..cee651054 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-pagination.md.dKBYttYP.js @@ -0,0 +1 @@ +import{_ as o,D as l,o as n,c,I as t,R as i,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const b=JSON.parse('{"title":"c-list-pagination","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.md"}'),p={name:"stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.md"},r=i('

c-list-pagination

A component that provides an interface for modifying the pagination parameters of a ListViewModel.

This is a composite of c-list-page-size, c-list-range-display, and c-list-page, arranged horizontally. It is designed to be used above or below a table (e.g. c-table).

Example Usage

template
<c-list-pagination :list="list" />

Props

',6),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" whose pagination will be editable.")],-1),u=e("p",null,[a("An optional list of available page sizes to offer through "),e("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.html"},"c-list-page-size"),a(". Defaults to "),e("code",null,"[10, 25, 100]"),a(".")],-1);function h(m,g,f,v,_,y){const s=l("Prop");return n(),c("div",null,[r,t(s,{def:"list: ListViewModel",lang:"ts"}),d,t(s,{def:"pageSizes?: number[]",lang:"ts"}),u])}const k=o(p,[["render",h]]);export{b as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-pagination.md.dKBYttYP.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-pagination.md.dKBYttYP.lean.js new file mode 100644 index 000000000..2a0121644 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-pagination.md.dKBYttYP.lean.js @@ -0,0 +1 @@ +import{_ as o,D as l,o as n,c,I as t,R as i,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const b=JSON.parse('{"title":"c-list-pagination","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.md"}'),p={name:"stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.md"},r=i("",6),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" whose pagination will be editable.")],-1),u=e("p",null,[a("An optional list of available page sizes to offer through "),e("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.html"},"c-list-page-size"),a(". Defaults to "),e("code",null,"[10, 25, 100]"),a(".")],-1);function h(m,g,f,v,_,y){const s=l("Prop");return n(),c("div",null,[r,t(s,{def:"list: ListViewModel",lang:"ts"}),d,t(s,{def:"pageSizes?: number[]",lang:"ts"}),u])}const k=o(p,[["render",h]]);export{b as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-range-display.md.ahES0O89.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-range-display.md.ahES0O89.js new file mode 100644 index 000000000..7e58be3de --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-range-display.md.ahES0O89.js @@ -0,0 +1 @@ +import{_ as t,D as o,o as l,c as n,I as c,R as i,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const v=JSON.parse('{"title":"c-list-range-display","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-range-display.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-range-display.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-list-range-display.md"},p=i('

c-list-range-display

Displays pagination information about the current $items of a ListViewModel in the format <start index> - <end index> of <total count>.

Uses the pagination information returned from the last successful $load call, not the current $params of the ListViewModel.

Examples

template
<c-list-range-display :list="list" />

Props

',6),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" to display pagination information for.")],-1);function u(m,h,y,_,f,D){const s=o("Prop");return l(),n("div",null,[p,c(s,{def:"list: ListViewModel",lang:"ts"}),d])}const k=t(r,[["render",u]]);export{v as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-range-display.md.ahES0O89.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-range-display.md.ahES0O89.lean.js new file mode 100644 index 000000000..7703117cb --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-list-range-display.md.ahES0O89.lean.js @@ -0,0 +1 @@ +import{_ as t,D as o,o as l,c as n,I as c,R as i,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const v=JSON.parse('{"title":"c-list-range-display","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-range-display.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-list-range-display.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-list-range-display.md"},p=i("",6),d=e("p",null,[a("The "),e("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),a(" to display pagination information for.")],-1);function u(m,h,y,_,f,D){const s=o("Prop");return l(),n("div",null,[p,c(s,{def:"list: ListViewModel",lang:"ts"}),d])}const k=t(r,[["render",u]]);export{v as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-loader-status.md.FwR65IYg.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-loader-status.md.FwR65IYg.js new file mode 100644 index 000000000..952e76760 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-loader-status.md.FwR65IYg.js @@ -0,0 +1,44 @@ +import{_ as l,D as t,o as p,c as r,I as a,R as o,k as e,a as n}from"./chunks/framework.g9eZ-ZSs.js";const F=JSON.parse('{"title":"c-loader-status","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-loader-status.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-loader-status.md"}'),c={name:"stacks/vue/coalesce-vue-vuetify/components/c-loader-status.md"},i=o(`

c-loader-status

A component for displaying progress and error information for one or more API Callers.

TIP

It is highly recommended that all API Callers utilized by your application that don't have any other kind of error handling should be represented by a c-loader-status so that users can be aware of any errors that occur.

Progress is indicated with a Vuetify v-progress-linear component, and errors are displayed in a v-alert. Transitions are applied to smoothly fade between the different states the the caller can be in.

Examples

Wrap contents of a details/edit page:

template
<h1>Person Details</h1>
+<c-loader-status
+    :loaders="{ 
+        'no-initial-content no-error-content': [person.$load],
+        '': [person.$save, person.$delete],
+    }"
+    #default
+>
+    First Name: {{ person.firstName }}
+    Last Name: {{ person.lastName }}
+    Employer: {{ person.company.name }}
+</c-loader-status>

Use c-loader-status to render a progress bar and any error messages, but don't use it to control content:

template
<c-loader-status :loaders="list.$load" />

Wrap a save/submit button:

template
<c-loader-status :loaders="[person.$save, person.$delete]" no-loading-content>
+    <button> Save </button>
+    <button> Delete </button>
+</c-loader-status>

Hides the table before the first load has completed, or if loading the list encountered an error. Don't show the progress bar after we've already loaded the list for the first time (useful for loads that occur without user interaction, e.g. setInterval):

template
<c-loader-status
+    :loaders="list.$load"
+    no-initial-content 
+    no-error-content
+    no-secondary-progress 
+>
+    <table>
+        <tr v-for="item in list.$items"> ... </tr>
+    </table>
+</c-loader-status>

Props

`,14),d=o(`

This prop has multiple options that support simple or complex usage scenarios:

Flags Per Component

A single instance, or array of API Callers, whose status will be represented by the component. The flags for these objects will be determined from the component-level flag props.

template
<c-loader-status
+  :loaders="[product.$load, person.$load]"
+  no-initial-content
+  no-error-content
+/>

Flags Per Caller

A more advanced usage allows passing different flags for different callers. Provide a dictionary object with entries mapping zero or more flags to one or more API Callers. Multiple entries of flags/caller pairs may be specified in the dictionary to give different behavior to different API callers. These flags are layered on top of the base flag props.

template
<c-loader-status
+  :loaders="{ 
+    'no-initial-content no-error-content': [person.$load],
+    'no-loading-content': [person.$save, person.$delete],
+  }"
+/>

`,2),D=e("p",null,"Specify if space should be reserved for the progress indicator. If set to false, the content in the default slot may jump up and down slightly as the progress indicator shows and hides.",-1),y=e("p",null,"Positions the progress bar absolutely. This can be useful in compact interfaces where extra space for the progress bar is undesirable, allowing the progress bar to potentially overlap content while active.",-1),h=e("p",null,[n("Specifies the height in pixels of the "),e("a",{href:"https://vuetifyjs.com/en/components/progress-linear",target:"_blank",rel:"noreferrer"},"v-progress-linear"),n(" used to indicate progress.")],-1),u=o('

Component level flags options that control behavior when the simple form of loaders (single instance or array) is used, as well as provide baseline defaults that can be overridden by the advanced form of loaders (object map) .

Flags

The available flags are as follows, all of which default to true. In the object literal syntax for loaders, the no- prefix may be omitted to set the flag to true.

Flag
Description
no-loading-contentControls whether the default slot is rendered while any API caller is loading (i.e. when caller.isLoading === true).
no-error-contentControls whether the default slot is rendered while any API Caller is in an error state (i.e. when caller.wasSuccessful === false).
no-initial-contentControls whether the default slot is rendered while any API Caller has yet to receive a response for the first time (i.e. when caller.wasSuccessful === null).
no-progressMaster toggle for whether the progress indicator is shown in any scenario.
no-initial-progressControls whether the progress indicator is shown when an API Caller is loading for the very first time (i.e. when caller.wasSuccessful === null).
no-secondary-progressControls whether the progress indicator is shown when an API Caller is loading any time after its first invocation (i.e. when caller.wasSuccessful !== null).

Slots

default - Accepts the content whose visibility is controlled by the state of the supplied API Callers. It will be shown or hidden according to the flags defined for each caller.

TIP

(Vue 2 Only): Define the default slot as a scoped slot (e.g. with #default or v-slot:default on the c-loader-status) to prevent the VNode tree from being created when the content should be hidden. This improves performance and helps avoid null reference errors that can be caused when trying to render objects that haven't been loaded yet.

',7);function C(f,g,m,b,v,_){const s=t("Prop");return p(),r("div",null,[i,a(s,{def:`loaders: + // Flags per component: + | ApiCaller + | ApiCaller[] + // Flags per caller: + | { [flags: string]: ApiCaller | ApiCaller[] } `,lang:"ts"}),d,a(s,{def:"progressPlaceholder: boolean = true",lang:"ts"}),D,a(s,{def:"progressAbsolute: boolean = false",lang:"ts"}),y,a(s,{def:"height: number = 10",lang:"ts"}),h,a(s,{def:` +no-loading-content?: boolean; +no-error-content?: boolean; +no-initial-content?: boolean; +no-progress?: boolean; +no-initial-progress?: boolean; +no-secondary-progress?: boolean;`,lang:"ts",id:"flags-props"}),u])}const w=l(c,[["render",C]]);export{F as __pageData,w as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-loader-status.md.FwR65IYg.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-loader-status.md.FwR65IYg.lean.js new file mode 100644 index 000000000..375d47832 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-loader-status.md.FwR65IYg.lean.js @@ -0,0 +1,12 @@ +import{_ as l,D as t,o as p,c as r,I as a,R as o,k as e,a as n}from"./chunks/framework.g9eZ-ZSs.js";const F=JSON.parse('{"title":"c-loader-status","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-loader-status.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-loader-status.md"}'),c={name:"stacks/vue/coalesce-vue-vuetify/components/c-loader-status.md"},i=o("",14),d=o("",2),D=e("p",null,"Specify if space should be reserved for the progress indicator. If set to false, the content in the default slot may jump up and down slightly as the progress indicator shows and hides.",-1),y=e("p",null,"Positions the progress bar absolutely. This can be useful in compact interfaces where extra space for the progress bar is undesirable, allowing the progress bar to potentially overlap content while active.",-1),h=e("p",null,[n("Specifies the height in pixels of the "),e("a",{href:"https://vuetifyjs.com/en/components/progress-linear",target:"_blank",rel:"noreferrer"},"v-progress-linear"),n(" used to indicate progress.")],-1),u=o("",7);function C(f,g,m,b,v,_){const s=t("Prop");return p(),r("div",null,[i,a(s,{def:`loaders: + // Flags per component: + | ApiCaller + | ApiCaller[] + // Flags per caller: + | { [flags: string]: ApiCaller | ApiCaller[] } `,lang:"ts"}),d,a(s,{def:"progressPlaceholder: boolean = true",lang:"ts"}),D,a(s,{def:"progressAbsolute: boolean = false",lang:"ts"}),y,a(s,{def:"height: number = 10",lang:"ts"}),h,a(s,{def:` +no-loading-content?: boolean; +no-error-content?: boolean; +no-initial-content?: boolean; +no-progress?: boolean; +no-initial-progress?: boolean; +no-secondary-progress?: boolean;`,lang:"ts",id:"flags-props"}),u])}const w=l(c,[["render",C]]);export{F as __pageData,w as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-many-to-many.md.kg745vjv.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-many-to-many.md.kg745vjv.js new file mode 100644 index 000000000..c896fa426 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-many-to-many.md.kg745vjv.js @@ -0,0 +1,10 @@ +import{_ as n,D as l,o as c,c as p,I as o,R as t,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-select-many-to-many","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-many-to-many.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-many-to-many.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-select-many-to-many.md"},d=t(`

c-select-many-to-many

A multi-select dropdown component that allows for selecting values fetched from the generated /list API endpoints for collection navigation properties that were annotated with [ManyToMany].

TIP

It is unlikely that you'll ever need to use this component directly - it is highly recommended that you use c-input instead and let it delegate to c-select-many-to-many for you.

Examples

template
<c-select-many-to-many :model="case" for="caseProducts" />
template
<c-select-many-to-many 
+    :model="case" 
+    for="caseProducts" 
+    dense
+    outlined
+/>
template
<c-select-many-to-many 
+    v-model="case.caseProducts" 
+    for="Case.caseProducts" 
+/>

Props

`,8),i=t('

A metadata specifier for the value being bound. One of:

  • A string with the name of the value belonging to model.
  • A direct reference to a metadata object.
  • A string in dot-notation that starts with a type name.

Note

c-select-many-to-many expects metadata for the "real" collection navigation property on a model. If you provide it the string you passed to [ManyToMany], an error wil be thrown.

',3),m=e("p",null,[a("An object owning the value that was specified by the "),e("code",null,"for"),a(" prop. If provided, the input will be bound to the corresponding property on the "),e("code",null,"model"),a(" object.")],-1),y=e("p",null,[a("If binding the component with "),e("code",null,"v-model"),a(", accepts the "),e("code",null,"value"),a(" part of "),e("code",null,"v-model"),a(".")],-1),u=e("p",null,[a("An optional set of "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Data Source Standard Parameters"),a(" to pass to API calls made to the server.")],-1),h=t('

If provided and non-false, enables response caching on the component's internal API caller.

Events

The following events and automatic API calls are only used when bound to a model that has auto-saves enabled.

  • adding - Fired when a new item has been selected, but before the call to /save has completed.
  • added - Fired when the call to /save has completed after adding a new item.
  • deleting - Fired when an item has been removed, but before the call to /delete has completed.
  • deleted - Fired when the call to /delete has completed after removing an item.
',4);function D(v,_,f,C,g,b){const s=l("Prop");return c(),p("div",null,[d,o(s,{def:"for: string | Property | Value",lang:"ts"}),i,o(s,{def:"model?: Model",lang:"ts"}),m,o(s,{def:`value?: any // Vue 2 +modelValue?: any // Vue 3`,lang:"ts"}),y,o(s,{def:"params?: ListParameters",lang:"ts"}),u,o(s,{def:"cache?: ResponseCachingConfiguration | boolean",lang:"ts"}),h])}const T=n(r,[["render",D]]);export{k as __pageData,T as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-many-to-many.md.kg745vjv.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-many-to-many.md.kg745vjv.lean.js new file mode 100644 index 000000000..31025b6b9 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-many-to-many.md.kg745vjv.lean.js @@ -0,0 +1,2 @@ +import{_ as n,D as l,o as c,c as p,I as o,R as t,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-select-many-to-many","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-many-to-many.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-many-to-many.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-select-many-to-many.md"},d=t("",8),i=t("",3),m=e("p",null,[a("An object owning the value that was specified by the "),e("code",null,"for"),a(" prop. If provided, the input will be bound to the corresponding property on the "),e("code",null,"model"),a(" object.")],-1),y=e("p",null,[a("If binding the component with "),e("code",null,"v-model"),a(", accepts the "),e("code",null,"value"),a(" part of "),e("code",null,"v-model"),a(".")],-1),u=e("p",null,[a("An optional set of "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Data Source Standard Parameters"),a(" to pass to API calls made to the server.")],-1),h=t("",4);function D(v,_,f,C,g,b){const s=l("Prop");return c(),p("div",null,[d,o(s,{def:"for: string | Property | Value",lang:"ts"}),i,o(s,{def:"model?: Model",lang:"ts"}),m,o(s,{def:`value?: any // Vue 2 +modelValue?: any // Vue 3`,lang:"ts"}),y,o(s,{def:"params?: ListParameters",lang:"ts"}),u,o(s,{def:"cache?: ResponseCachingConfiguration | boolean",lang:"ts"}),h])}const T=n(r,[["render",D]]);export{k as __pageData,T as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-string-value.md.tV0v8rsN.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-string-value.md.tV0v8rsN.js new file mode 100644 index 000000000..4b8afc3ce --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-string-value.md.tV0v8rsN.js @@ -0,0 +1,27 @@ +import{_ as l,D as o,o as t,c as p,I as e,R as c,k as s,a}from"./chunks/framework.g9eZ-ZSs.js";const T=JSON.parse('{"title":"c-select-string-value","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-string-value.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-string-value.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-select-string-value.md"},D=c(`

c-select-string-value

A dropdown component that will present a list of suggested string values from a custom API endpoint. Allows users to input values that aren't provided by the endpoint.

Effectively, this is a server-driven autocomplete list.

Examples

template
<c-select-string-value 
+    :model="person" 
+    for="jobTitle"
+    method="getSuggestedJobTitles"
+/>
template
<c-select-string-value 
+    v-model="title"
+    label="Job Title"
+    for="Person"
+    method="getSuggestedJobTitles"
+/>
c#
class Person 
+{
+    public int PersonId { get; set; } 
+
+    public string JobTitle { get; set; }
+
+    [Coalesce]
+    public static async Task<ICollection<string>> GetSuggestedJobTitles(AppDbContext db, string search) 
+    {
+        return await db.People
+            .Select(p => p.JobTitle)
+            .Distinct()
+            .Where(t => t.StartsWith(search))
+            .OrderBy(t => t)
+            .Take(100)
+            .ToListAsync()
+    }
+}

Props

`,8),i=s("p",null,"A metadata specifier for the value being bound. One of:",-1),y=s("ul",null,[s("li",null,[a("A string with the name of the value belonging to "),s("code",null,"model"),a(".")]),s("li",null,"A direct reference to a metadata object."),s("li",null,"A string in dot-notation that starts with a type name.")],-1),d=s("p",null,[a("An object owning the value that was specified by the "),s("code",null,"for"),a(" prop. If provided, the input will be bound to the corresponding property on the "),s("code",null,"model"),a(" object.")],-1),u=s("p",null,[a("The camel-cased name of the "),s("a",{href:"/Coalesce/modeling/model-components/methods.html"},"Custom Method"),a(" to invoke to get the list of valid values. Will be passed a single string parameter "),s("code",null,"search"),a(". Must be a static method on the type of the provided "),s("code",null,"model"),a(" object that returns a collection of strings.")],-1),C=s("p",null,[a("An optional set of "),s("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Data Source Standard Parameters"),a(" to pass to API calls made to the server.")],-1),h=s("p",null,"True if the method should be invoked and the list displayed when the entered search term is blank.",-1),g=s("p",null,"True if the bound value should be updated as the user types. Otherwise, the bound value is updated when focus is lost or when a suggested value is chosen. This is only applicable for Vuetify 2 - in Vuetify 3, this is the default behavior.",-1);function m(v,f,b,_,E,A){const n=o("Prop");return t(),p("div",null,[D,e(n,{def:"for: string | Property | Value",lang:"ts"}),i,y,e(n,{def:"model: Model",lang:"ts"}),d,e(n,{def:"method: string",lang:"ts"}),u,e(n,{def:"params?: DataSourceParameters",lang:"ts"}),C,e(n,{def:"listWhenEmpty?: boolean = false",lang:"ts"}),h,e(n,{def:"eager?: boolean = false",lang:"ts"}),g])}const k=l(r,[["render",m]]);export{T as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-string-value.md.tV0v8rsN.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-string-value.md.tV0v8rsN.lean.js new file mode 100644 index 000000000..0791e40f9 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-string-value.md.tV0v8rsN.lean.js @@ -0,0 +1 @@ +import{_ as l,D as o,o as t,c as p,I as e,R as c,k as s,a}from"./chunks/framework.g9eZ-ZSs.js";const T=JSON.parse('{"title":"c-select-string-value","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-string-value.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-string-value.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-select-string-value.md"},D=c("",8),i=s("p",null,"A metadata specifier for the value being bound. One of:",-1),y=s("ul",null,[s("li",null,[a("A string with the name of the value belonging to "),s("code",null,"model"),a(".")]),s("li",null,"A direct reference to a metadata object."),s("li",null,"A string in dot-notation that starts with a type name.")],-1),d=s("p",null,[a("An object owning the value that was specified by the "),s("code",null,"for"),a(" prop. If provided, the input will be bound to the corresponding property on the "),s("code",null,"model"),a(" object.")],-1),u=s("p",null,[a("The camel-cased name of the "),s("a",{href:"/Coalesce/modeling/model-components/methods.html"},"Custom Method"),a(" to invoke to get the list of valid values. Will be passed a single string parameter "),s("code",null,"search"),a(". Must be a static method on the type of the provided "),s("code",null,"model"),a(" object that returns a collection of strings.")],-1),C=s("p",null,[a("An optional set of "),s("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Data Source Standard Parameters"),a(" to pass to API calls made to the server.")],-1),h=s("p",null,"True if the method should be invoked and the list displayed when the entered search term is blank.",-1),g=s("p",null,"True if the bound value should be updated as the user types. Otherwise, the bound value is updated when focus is lost or when a suggested value is chosen. This is only applicable for Vuetify 2 - in Vuetify 3, this is the default behavior.",-1);function m(v,f,b,_,E,A){const n=o("Prop");return t(),p("div",null,[D,e(n,{def:"for: string | Property | Value",lang:"ts"}),i,y,e(n,{def:"model: Model",lang:"ts"}),d,e(n,{def:"method: string",lang:"ts"}),u,e(n,{def:"params?: DataSourceParameters",lang:"ts"}),C,e(n,{def:"listWhenEmpty?: boolean = false",lang:"ts"}),h,e(n,{def:"eager?: boolean = false",lang:"ts"}),g])}const k=l(r,[["render",m]]);export{T as __pageData,k as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-values.md.mjZZaKzu.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-values.md.mjZZaKzu.js new file mode 100644 index 000000000..109757af6 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-values.md.mjZZaKzu.js @@ -0,0 +1,5 @@ +import{_ as l,D as o,o as n,c,I as t,R as p,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-select-values","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-values.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-values.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-select-values.md"},i=p(`

c-select-values

A multi-select input component for collections of non-object values (primarily strings and numbers).

TIP

It is unlikely that you'll ever need to use this component directly - it is highly recommended that you use c-input instead and let it delegate to c-select-values for you.

Examples

template
<c-select-values 
+    :model="post.setTags.args" 
+    for="Post.methods.setTags.params.tagNames" 
+/>

Props

`,6),u=e("p",null,"A metadata specifier for the value being bound. One of:",-1),d=e("ul",null,[e("li",null,[s("A string with the name of the value belonging to "),e("code",null,"model"),s(".")]),e("li",null,"A direct reference to a metadata object."),e("li",null,"A string in dot-notation that starts with a type name.")],-1),m=e("p",null,[s("An object owning the value that was specified by the "),e("code",null,"for"),s(" prop.")],-1),h=e("p",null,[s("If binding the component with "),e("code",null,"v-model"),s(", accepts the "),e("code",null,"value"),s(" part of "),e("code",null,"v-model"),s(".")],-1);function v(D,y,f,_,g,C){const a=o("Prop");return n(),c("div",null,[i,t(a,{def:"for: string | CollectionProperty | CollectionValue",lang:"ts"}),u,d,t(a,{def:"model?: Model",lang:"ts"}),m,t(a,{def:`value?: any // Vue 2 +modelValue?: any // Vue 3`,lang:"ts"}),h])}const P=l(r,[["render",v]]);export{k as __pageData,P as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-values.md.mjZZaKzu.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-values.md.mjZZaKzu.lean.js new file mode 100644 index 000000000..cad4fc4bf --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select-values.md.mjZZaKzu.lean.js @@ -0,0 +1,2 @@ +import{_ as l,D as o,o as n,c,I as t,R as p,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-select-values","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-values.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-select-values.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-select-values.md"},i=p("",6),u=e("p",null,"A metadata specifier for the value being bound. One of:",-1),d=e("ul",null,[e("li",null,[s("A string with the name of the value belonging to "),e("code",null,"model"),s(".")]),e("li",null,"A direct reference to a metadata object."),e("li",null,"A string in dot-notation that starts with a type name.")],-1),m=e("p",null,[s("An object owning the value that was specified by the "),e("code",null,"for"),s(" prop.")],-1),h=e("p",null,[s("If binding the component with "),e("code",null,"v-model"),s(", accepts the "),e("code",null,"value"),s(" part of "),e("code",null,"v-model"),s(".")],-1);function v(D,y,f,_,g,C){const a=o("Prop");return n(),c("div",null,[i,t(a,{def:"for: string | CollectionProperty | CollectionValue",lang:"ts"}),u,d,t(a,{def:"model?: Model",lang:"ts"}),m,t(a,{def:`value?: any // Vue 2 +modelValue?: any // Vue 3`,lang:"ts"}),h])}const P=l(r,[["render",v]]);export{k as __pageData,P as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-select.md.sYvwmnUu.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select.md.sYvwmnUu.js new file mode 100644 index 000000000..65530c6df --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select.md.sYvwmnUu.js @@ -0,0 +1,35 @@ +import{_ as t,D as l,o as p,c,I as a,k as e,R as o,a as s}from"./chunks/framework.g9eZ-ZSs.js";const R=JSON.parse('{"title":"c-select","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-select.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-select.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-select.md"},i=o(`

c-select

A dropdown component that allows for selecting values fetched from the generated /list API endpoints.

Used both for selecting values for foreign key and navigation properties, and for selecting arbitrary objects or primary keys independent of a parent or owning object.

Examples

Binding to a navigation property or foreign key of a model:

template
  <c-select :model="person" for="company" />
+  <!-- OR: -->
+  <c-select :model="person" for="companyId" />

Binding an arbitrary primary key value or an arbitrary object:

template
  <!-- Binding a key: -->
+  <c-select for="Person" :key-value.sync="selectedPersonId" />
+
+  <!-- Binding an object: -->
+  <c-select for="Person" :object-value.sync="selectedPerson" />
+  <c-select for="Person" v-model="selectedPerson" />

Examples of other props:

template
<c-select 
+  for="Person" 
+  v-model="selectedPerson"
+  :clearable="false"
+  preselect-first
+  :params="{ pageSize: 42, filter: { isActive: true } }"
+  :create="createMethods"
+  dense
+  outlined
+  color="pink"
+/>
+<!-- \`createMethods\` is defined in the docs of \`create\` below -->

Props

`,11),D=e("p",null,"A metadata specifier for the value being bound. One of:",-1),d=e("ul",null,[e("li",null,[s("The name of a foreign key or reference navigation property belonging to "),e("code",null,"model"),s(".")]),e("li",null,"The name of a model type."),e("li",null,"A direct reference to a metadata object."),e("li",null,"A string in dot-notation that starts with a type name that resolves to a foreign key or reference navigation property.")],-1),y=e("div",{class:"tip custom-block"},[e("p",{class:"custom-block-title"},"TIP"),e("p",null,"When binding by a key value, if the corresponding object cannot be found (e.g. there is no navigation property, or the navigation property is null), c-select will automatically attempt to load the object from the server so it can be displayed in the UI.")],-1),u=e("p",null,[s("An object owning the value that was specified by the "),e("code",null,"for"),s(" prop. If provided, the input will be bound to the corresponding property on the "),e("code",null,"model"),s(" object.")],-1),h=e("p",null,[s("If "),e("code",null,"for"),s(" specifies a foreign key or reference navigation property, both the foreign key and the navigation property of the "),e("code",null,"model"),s(" will be updated when the selected value is changed.")],-1),C=e("p",null,[s("When binding the component with "),e("code",null,"v-model"),s(", accepts the "),e("code",null,"value"),s(" part of "),e("code",null,"v-model"),s(". If "),e("code",null,"for"),s(" was specified as a foreign key, this will expect a key; likewise, if "),e("code",null,"for"),s(" was specified as a type or as a navigation property, this will expect an object.")],-1),f=e("p",null,[s("When bound with "),e("code",null,':key-value.sync="keyValue"'),s(", allows binding the primary key of the selected object explicitly.")],-1),m=e("p",null,[s("When bound with "),e("code",null,':object-value.sync="objectValue"'),s(", allows binding the selected object explicitly.")],-1),g=e("p",null,[s("Whether the selection can be cleared or not, emitting "),e("code",null,"null"),s(" as the input value.")],-1),b=e("p",null,[s("If not specified and the component is bound to a foreign key or reference navigation property, defaults to whether or not the foreign key has a "),e("code",null,"required"),s(" validation rule defined in its "),e("a",{href:"/Coalesce/stacks/vue/layers/metadata.html"},"Metadata"),s(".")],-1),_=e("p",null,"If true, then when the first list results for the component are received by the client just after the component is created, c-select will emit the first item in the list as the selected value.",-1),v=e("p",null,"If true, then when the first list results for the component are received by the client just after the component is created, if the results contained exactly one item, c-select will emit that only item as the selected value.",-1),E=e("p",null,[s("If true, the list results will be reloaded when the dropdown menu is opened. By default, list results are loaded when the component is mounted and also when any of its parameters change (either search input or the "),e("code",null,"params"),s(" prop).")],-1),w=e("p",null,[s("An optional set of "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Data Source Standard Parameters"),s(" to pass to API calls made to the server.")],-1),F=e("p",null,[s("If provided and non-false, enables "),e("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#response-caching"},"response caching"),s(" on the component's internal API callers.")],-1),k=e("p",null,"A object containing a pair of methods that allowing users to create new items from directly within the c-select if a matching object is not found.",-1),q=e("p",null,[s("The object must contain the following two methods. You should define these in your component's "),e("code",null,"script"),s(" section - don't try to define them inline in your component.")],-1),A={style:{"margin-left":"20px"}},P=e("p",null,"A function that will be called with the user's current search term, as well as the collection of currently loaded items being presented to the user as valid selection options.",-1),I=e("p",null,[s("It should return either a "),e("code",null,"string"),s(" that will be presented to the user as an option in the dropdown that can be clicked to invoke the "),e("code",null,"getItem"),s(" function below, or it should return "),e("code",null,"false"),s(" to prevent such an option from being shown to the user.")],-1),x=e("p",null,[s("A function that will be invoked when the user clicks the option in the dropdown list described by "),e("code",null,"getLabel"),s(". It will be given the user's current search term as well as the value of the label returned from "),e("code",null,"getLabel"),s(" as parameters. It must perform the necessary operations to create the new object on the server and then return a reference to that object.")],-1),j=o(`

For example:

ts
createMethods = {
+  getLabel(search: string, items: Person[]) {
+    const searchLower = search.toLowerCase();
+    if (items.some(a => a.name?.toLowerCase().indexOf(searchLower) == 0)) {
+      return false;
+    }
+    return search;
+  },
+  async getItem(search: string, label: string) {
+    const client = new PersonApiClient();
+    return (await client.addPersonByName(label)).data.object!;
+  }
+}

Slots

#item="{ item, search }" - Slot used to customize the text of both items inside the list, as well as the text of selected items. By default, items are rendered with c-display. Slot is passed a parameter item containing a model instance, and search containing the current search query.

#list-item="{ item, search }" - Slot used to customize the text of items inside the list. If not provided, falls back to the item slot.

#selected-item="{ item, search }" - Slot used to customize the text of selected items. If not provided, falls back to the item slot.

`,6);function T(S,B,V,L,M,N){const n=l("Prop");return p(),c("div",null,[i,a(n,{def:"for: string | ForeignKeyProperty | ModelReferenceNavigationProperty | ModelType",lang:"ts"}),D,d,y,a(n,{def:"model?: Model",lang:"ts"}),u,h,a(n,{def:`value?: any // Vue 2 +modelValue?: any // Vue 3`,lang:"ts"}),C,a(n,{def:"keyValue?: any",lang:"ts"}),f,a(n,{def:"objectValue?: any",lang:"ts"}),m,a(n,{def:"clearable?: boolean",lang:"ts"}),g,b,a(n,{def:"preselectFirst?: boolean = false",lang:"ts"}),_,a(n,{def:"preselectSingle?: boolean = false",lang:"ts"}),v,a(n,{def:"reloadOnOpen?: boolean = false",lang:"ts"}),E,a(n,{def:"params?: ListParameters",lang:"ts"}),w,a(n,{def:"cache?: ResponseCachingConfiguration | boolean",lang:"ts"}),F,a(n,{def:`create?: { + getLabel: (search: string, items: TModel[]) => string | false, + getItem: (search: string, label: string) => Promise +}`,lang:"ts"}),k,q,e("div",A,[a(n,{def:"create.getLabel: (search: string, items: TModel[]) => string | false",lang:"ts",id:"member-create-getLabel"}),P,I,a(n,{def:"create.getItem: (search: string, label: string) => Promise",lang:"ts",id:"member-create-getItem"}),x]),j])}const W=t(r,[["render",T]]);export{R as __pageData,W as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-select.md.sYvwmnUu.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select.md.sYvwmnUu.lean.js new file mode 100644 index 000000000..6f8f1f92a --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-select.md.sYvwmnUu.lean.js @@ -0,0 +1,5 @@ +import{_ as t,D as l,o as p,c,I as a,k as e,R as o,a as s}from"./chunks/framework.g9eZ-ZSs.js";const R=JSON.parse('{"title":"c-select","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-select.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-select.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-select.md"},i=o("",11),D=e("p",null,"A metadata specifier for the value being bound. One of:",-1),d=e("ul",null,[e("li",null,[s("The name of a foreign key or reference navigation property belonging to "),e("code",null,"model"),s(".")]),e("li",null,"The name of a model type."),e("li",null,"A direct reference to a metadata object."),e("li",null,"A string in dot-notation that starts with a type name that resolves to a foreign key or reference navigation property.")],-1),y=e("div",{class:"tip custom-block"},[e("p",{class:"custom-block-title"},"TIP"),e("p",null,"When binding by a key value, if the corresponding object cannot be found (e.g. there is no navigation property, or the navigation property is null), c-select will automatically attempt to load the object from the server so it can be displayed in the UI.")],-1),u=e("p",null,[s("An object owning the value that was specified by the "),e("code",null,"for"),s(" prop. If provided, the input will be bound to the corresponding property on the "),e("code",null,"model"),s(" object.")],-1),h=e("p",null,[s("If "),e("code",null,"for"),s(" specifies a foreign key or reference navigation property, both the foreign key and the navigation property of the "),e("code",null,"model"),s(" will be updated when the selected value is changed.")],-1),C=e("p",null,[s("When binding the component with "),e("code",null,"v-model"),s(", accepts the "),e("code",null,"value"),s(" part of "),e("code",null,"v-model"),s(". If "),e("code",null,"for"),s(" was specified as a foreign key, this will expect a key; likewise, if "),e("code",null,"for"),s(" was specified as a type or as a navigation property, this will expect an object.")],-1),f=e("p",null,[s("When bound with "),e("code",null,':key-value.sync="keyValue"'),s(", allows binding the primary key of the selected object explicitly.")],-1),m=e("p",null,[s("When bound with "),e("code",null,':object-value.sync="objectValue"'),s(", allows binding the selected object explicitly.")],-1),g=e("p",null,[s("Whether the selection can be cleared or not, emitting "),e("code",null,"null"),s(" as the input value.")],-1),b=e("p",null,[s("If not specified and the component is bound to a foreign key or reference navigation property, defaults to whether or not the foreign key has a "),e("code",null,"required"),s(" validation rule defined in its "),e("a",{href:"/Coalesce/stacks/vue/layers/metadata.html"},"Metadata"),s(".")],-1),_=e("p",null,"If true, then when the first list results for the component are received by the client just after the component is created, c-select will emit the first item in the list as the selected value.",-1),v=e("p",null,"If true, then when the first list results for the component are received by the client just after the component is created, if the results contained exactly one item, c-select will emit that only item as the selected value.",-1),E=e("p",null,[s("If true, the list results will be reloaded when the dropdown menu is opened. By default, list results are loaded when the component is mounted and also when any of its parameters change (either search input or the "),e("code",null,"params"),s(" prop).")],-1),w=e("p",null,[s("An optional set of "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Data Source Standard Parameters"),s(" to pass to API calls made to the server.")],-1),F=e("p",null,[s("If provided and non-false, enables "),e("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#response-caching"},"response caching"),s(" on the component's internal API callers.")],-1),k=e("p",null,"A object containing a pair of methods that allowing users to create new items from directly within the c-select if a matching object is not found.",-1),q=e("p",null,[s("The object must contain the following two methods. You should define these in your component's "),e("code",null,"script"),s(" section - don't try to define them inline in your component.")],-1),A={style:{"margin-left":"20px"}},P=e("p",null,"A function that will be called with the user's current search term, as well as the collection of currently loaded items being presented to the user as valid selection options.",-1),I=e("p",null,[s("It should return either a "),e("code",null,"string"),s(" that will be presented to the user as an option in the dropdown that can be clicked to invoke the "),e("code",null,"getItem"),s(" function below, or it should return "),e("code",null,"false"),s(" to prevent such an option from being shown to the user.")],-1),x=e("p",null,[s("A function that will be invoked when the user clicks the option in the dropdown list described by "),e("code",null,"getLabel"),s(". It will be given the user's current search term as well as the value of the label returned from "),e("code",null,"getLabel"),s(" as parameters. It must perform the necessary operations to create the new object on the server and then return a reference to that object.")],-1),j=o("",6);function T(S,B,V,L,M,N){const n=l("Prop");return p(),c("div",null,[i,a(n,{def:"for: string | ForeignKeyProperty | ModelReferenceNavigationProperty | ModelType",lang:"ts"}),D,d,y,a(n,{def:"model?: Model",lang:"ts"}),u,h,a(n,{def:`value?: any // Vue 2 +modelValue?: any // Vue 3`,lang:"ts"}),C,a(n,{def:"keyValue?: any",lang:"ts"}),f,a(n,{def:"objectValue?: any",lang:"ts"}),m,a(n,{def:"clearable?: boolean",lang:"ts"}),g,b,a(n,{def:"preselectFirst?: boolean = false",lang:"ts"}),_,a(n,{def:"preselectSingle?: boolean = false",lang:"ts"}),v,a(n,{def:"reloadOnOpen?: boolean = false",lang:"ts"}),E,a(n,{def:"params?: ListParameters",lang:"ts"}),w,a(n,{def:"cache?: ResponseCachingConfiguration | boolean",lang:"ts"}),F,a(n,{def:`create?: { + getLabel: (search: string, items: TModel[]) => string | false, + getItem: (search: string, label: string) => Promise +}`,lang:"ts"}),k,q,e("div",A,[a(n,{def:"create.getLabel: (search: string, items: TModel[]) => string | false",lang:"ts",id:"member-create-getLabel"}),P,I,a(n,{def:"create.getItem: (search: string, label: string) => Promise",lang:"ts",id:"member-create-getItem"}),x]),j])}const W=t(r,[["render",T]]);export{R as __pageData,W as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-table.md.n25jKYu7.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-table.md.n25jKYu7.js new file mode 100644 index 000000000..f1d4212fb --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-table.md.n25jKYu7.js @@ -0,0 +1,17 @@ +import{_ as o,D as t,o as p,c,I as l,R as n,k as s,a as e}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-table","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-table.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-table.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-table.md"},i=n(`

c-table

A table component for displaying the contents of a ListViewModel. Also supports modifying the list's sort parameters by clicking on column headers. Pairs well with a c-list-pagination.

Example Usage

A simple table, rendering the items of a ListViewModel:

template
<c-table :list="list" />

A more complex example using more of the available options:

template
<c-table
+  :list="list"
+  :props="['firstName', 'lastName']"
+  :extra-headers="['Actions']"
+>
+  <template #item.append="{item}"> 
+    <td>
+      <v-btn
+        title="Edit"
+        text icon
+        :to="{name: 'edit-person', params: { id: item.$primaryKey }}"
+      >
+        <i class="fa fa-edit"></i>
+      </v-btn>
+    </td>
+  </template>
+</c-table>

Props

`,8),D=s("p",null,[e("The "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),e(" to display pagination information for.")],-1),d=s("p",null,"If provided, specifies which properties, and their ordering, should be given a column in the table.",-1),y=s("p",null,[e("If not provided, all non-key columns that aren't annotated with "),s("a",{href:"/Coalesce/modeling/model-components/attributes/hidden.html"},"[Hidden(HiddenAttribute.Areas.List)]"),e(" are given a column.")],-1),h=s("p",null,[e("The text contents of one or more extra "),s("code",null,"th"),e(" elements to render in the table. Should be used in conjunction with the "),s("code",null,"item.append"),e(" slot.")],-1),u=s("p",null,[e("If true, properties in each table cell will be rendered with "),s("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-input.html"},"c-input"),e(". Non-editable properties will be rendered in accordance with the value of the "),s("code",null,"admin"),e(" prop.")],-1),m=n('

If true, properties in each table cell will be rendered with c-admin-display instead of c-display.

Slots

item.append - A slot rendered after the td elements on each row that render the properties of each item in the table. Should be provided zero or more additional td elements. The number should match the number of additional headers provided to the extraHeaders prop.

',3);function C(f,_,b,v,g,E){const a=t("Prop");return p(),c("div",null,[i,l(a,{def:"list: ListViewModel",lang:"ts"}),D,l(a,{def:"props?: string[]",lang:"ts"}),d,y,l(a,{def:"extraHeaders?: string[]",lang:"ts"}),h,l(a,{def:"editable: boolean = false",lang:"ts"}),u,l(a,{def:"admin: boolean = false",lang:"ts"}),m])}const x=o(r,[["render",C]]);export{k as __pageData,x as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_components_c-table.md.n25jKYu7.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_components_c-table.md.n25jKYu7.lean.js new file mode 100644 index 000000000..e71f3bfdc --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_components_c-table.md.n25jKYu7.lean.js @@ -0,0 +1 @@ +import{_ as o,D as t,o as p,c,I as l,R as n,k as s,a as e}from"./chunks/framework.g9eZ-ZSs.js";const k=JSON.parse('{"title":"c-table","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/components/c-table.md","filePath":"stacks/vue/coalesce-vue-vuetify/components/c-table.md"}'),r={name:"stacks/vue/coalesce-vue-vuetify/components/c-table.md"},i=n("",8),D=s("p",null,[e("The "),s("a",{href:"/Coalesce/stacks/vue/layers/viewmodels.html"},"ListViewModel"),e(" to display pagination information for.")],-1),d=s("p",null,"If provided, specifies which properties, and their ordering, should be given a column in the table.",-1),y=s("p",null,[e("If not provided, all non-key columns that aren't annotated with "),s("a",{href:"/Coalesce/modeling/model-components/attributes/hidden.html"},"[Hidden(HiddenAttribute.Areas.List)]"),e(" are given a column.")],-1),h=s("p",null,[e("The text contents of one or more extra "),s("code",null,"th"),e(" elements to render in the table. Should be used in conjunction with the "),s("code",null,"item.append"),e(" slot.")],-1),u=s("p",null,[e("If true, properties in each table cell will be rendered with "),s("a",{href:"/Coalesce/stacks/vue/coalesce-vue-vuetify/components/c-input.html"},"c-input"),e(". Non-editable properties will be rendered in accordance with the value of the "),s("code",null,"admin"),e(" prop.")],-1),m=n("",3);function C(f,_,b,v,g,E){const a=t("Prop");return p(),c("div",null,[i,l(a,{def:"list: ListViewModel",lang:"ts"}),D,l(a,{def:"props?: string[]",lang:"ts"}),d,y,l(a,{def:"extraHeaders?: string[]",lang:"ts"}),h,l(a,{def:"editable: boolean = false",lang:"ts"}),u,l(a,{def:"admin: boolean = false",lang:"ts"}),m])}const x=o(r,[["render",C]]);export{k as __pageData,x as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_overview.md.SycRM1Rq.js b/assets/stacks_vue_coalesce-vue-vuetify_overview.md.SycRM1Rq.js new file mode 100644 index 000000000..e671f1202 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_overview.md.SycRM1Rq.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,R as o}from"./chunks/framework.g9eZ-ZSs.js";const f=JSON.parse('{"title":"Vuetify Components","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/overview.md","filePath":"stacks/vue/coalesce-vue-vuetify/overview.md"}'),s={name:"stacks/vue/coalesce-vue-vuetify/overview.md"},n=o('

Vuetify Components

The Vue stack for Coalesce provides a set of components based on Vuetify, packaged up in an NPM package coalesce-vue-vuetify2 or coalesce-vue-vuetify3. These components are driven primarily by the Metadata Layer, and include both low level input and display components like c-input and c-display that are highly reusable in the custom pages you'll build in your application, as well as high-level components like c-admin-table-page and c-admin-editor-page that constitute entire pages.

Setup

All Coalesce projects should be started from the template described in Getting Started with Vue, and will therefore have all the setup completed for you.

If for whatever reason you find yourself adding Coalesce to an existing project, use the template as a reference for what configuration needs to be added to your project.

Display Components

ComponentDescription

c-display

A general-purpose component for displaying any Value by rendering the value to a string with the display functions from the Models Layer. For plain string and number values, usage of this component is largely superfluous. For all other value types including dates, booleans, enums, objects, and collections, it is very handy.

c-loader-status

A component for displaying progress and error information for one or more API Callers.

TIP

It is highly recommended that all API Callers utilized by your application that don't have any other kind of error handling should be represented by a c-loader-status so that users can be aware of any errors that occur.

c-list-range-display

Displays pagination information about the current $items of a ListViewModel in the format <start index> - <end index> of <total count>.

c-table

A table component for displaying the contents of a ListViewModel. Also supports modifying the list's sort parameters by clicking on column headers. Pairs well with a c-list-pagination.

Input Components

ComponentDescription

c-input

A general-purpose input component for most Values. c-input does not have much functionality of its own - instead, it delegates to the right kind of component based on the type of value to which it is bound. This includes both other Coalesce Vuetify Components as well as direct usages of some Vuetify components.

c-select

A dropdown component that allows for selecting values fetched from the generated /list API endpoints.

Used both for selecting values for foreign key and navigation properties, and for selecting arbitrary objects or primary keys independent of a parent or owning object.

c-datetime-picker

A general, all-purpose date/time input component that can be used either with models and metadata or as a standalone component using only v-model.

c-select-many-to-many

A multi-select dropdown component that allows for selecting values fetched from the generated /list API endpoints for collection navigation properties that were annotated with [ManyToMany].

c-select-string-value

A dropdown component that will present a list of suggested string values from a custom API endpoint. Allows users to input values that aren't provided by the endpoint.

Effectively, this is a server-driven autocomplete list.

c-select-values

A multi-select input component for collections of non-object values (primarily strings and numbers).

c-list-filters

A component that provides an interface for modifying the filters prop of a ListViewModel's parameters.

c-list-pagination

A component that provides an interface for modifying the pagination parameters of a ListViewModel.

This is a composite of c-list-page-size, c-list-range-display, and c-list-page, arranged horizontally. It is designed to be used above or below a table (e.g. c-table).

c-list-page-size

A component that provides an dropdown for modifying the pageSize parameter prop of a ListViewModel.

c-list-page

A component that provides previous/next buttons and a text field for modifying the page parameter prop of a ListViewModel.

Admin Components

ComponentDescription

c-admin-method

Provides an interface for invoking a method and rendering its result, designed to be use in an admin page.

c-admin-methods

Renders in a Vuetify v-expansion-panels a c-admin-method for each method on a ViewModel or ListViewModel.

c-admin-display

Behaves the same as c-display, except any collection navigation properties will be rendered as links to an admin list page, and any models will be rendered as a link to an admin item page.

c-admin-editor

An editor for a single ViewModel instance. Provides a c-input for each property of the model.

c-admin-editor-page

A page for a creating/editing single ViewModel instance. Provides a c-admin-editor and a c-admin-methods for the instance. Designed to be routed to directly with vue-router.

c-admin-table

An full-featured table for a ListViewModel, including a c-admin-table-toolbar, c-table, and c-list-pagination.

c-admin-table-toolbar

A full-featured toolbar for a ListViewModel designed to be used on an admin page, including "Create" and "Reload" buttons, a c-list-range-display, a c-list-page, a search field, c-list-filters, and a c-list-page-size.

c-admin-table-page

A full-featured page for interacting with a ListViewModel. Provides a c-admin-table and a c-admin-methods for the list. Designed to be routed to directly with vue-router.

c-admin-audit-log-page

A full-featured page for interacting with Coalesce's Audit Logging. Presents a view similar to c-admin-table-page with content optimized for viewing audit log records. Designed to be routed to directly with vue-router.

',12),l=[n];function r(c,i,d,p,m,h){return t(),a("div",null,l)}const v=e(s,[["render",r]]);export{f as __pageData,v as default}; diff --git a/assets/stacks_vue_coalesce-vue-vuetify_overview.md.SycRM1Rq.lean.js b/assets/stacks_vue_coalesce-vue-vuetify_overview.md.SycRM1Rq.lean.js new file mode 100644 index 000000000..31078b887 --- /dev/null +++ b/assets/stacks_vue_coalesce-vue-vuetify_overview.md.SycRM1Rq.lean.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,R as o}from"./chunks/framework.g9eZ-ZSs.js";const f=JSON.parse('{"title":"Vuetify Components","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/coalesce-vue-vuetify/overview.md","filePath":"stacks/vue/coalesce-vue-vuetify/overview.md"}'),s={name:"stacks/vue/coalesce-vue-vuetify/overview.md"},n=o("",12),l=[n];function r(c,i,d,p,m,h){return t(),a("div",null,l)}const v=e(s,[["render",r]]);export{f as __pageData,v as default}; diff --git a/assets/stacks_vue_getting-started.md.2JJlpDJm.js b/assets/stacks_vue_getting-started.md.2JJlpDJm.js new file mode 100644 index 000000000..787fe5c06 --- /dev/null +++ b/assets/stacks_vue_getting-started.md.2JJlpDJm.js @@ -0,0 +1,39 @@ +import{_ as s,o as a,c as e,R as n}from"./chunks/framework.g9eZ-ZSs.js";const C=JSON.parse('{"title":"Getting Started","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/getting-started.md","filePath":"stacks/vue/getting-started.md"}'),o={name:"stacks/vue/getting-started.md"},l=n(`

Getting Started

Creating a Project

The quickest and easiest way to create a new Coalesce Vue application is to use the dotnet new template. In your favorite shell:

sh
dotnet new install IntelliTect.Coalesce.Vue.Template
+dotnet new coalescevue -o MyCompany.MyProject
+cd MyCompany.MyProject/*.Web
+npm ci
  Static Badge

Project Structure

Important

The Vue template is based on Vite. You are strongly encouraged to read through at least the first few pages of the Vite Documentation before getting started on any development.

The structure of the Web project follows the conventions of both ASP.NET Core and Vite. The Vue-specific folders are as follows:

  • /src - Files that should be compiled into your application. CSS/SCSS, TypeScript, Vue SFCs, and so on.
  • /public - Static assets that should be served as files. Includes index.html, the root document of the application.
  • /wwwroot - Target for compiled output.

During development, no special tooling is required to build your frontend code. Coalesce's UseViteDevelopmentServer in ASP.NET Core will take care of that automatically when the application starts. Just make sure NPM packages have been installed (npm ci).

Data Modeling

At this point, you can open up the newly-created solution in Visual Studio and run your application. However, your application won't do much without a data model, so you will probably want to do the following before running:

  • Create an initial Data Model by adding EF entity classes to the data project and the corresponding DbSet<> properties to AppDbContext. You will notice that the starter project includes a single model, Widget, to start with. Feel free to change this model or remove it entirely. Read Entity Models for more information about creating a data model.

  • Run dotnet ef migrations add Init (Init can be any name) in the data project to create an initial database migration.

  • Run Coalesce's code generation by either:

    • Running dotnet coalesce in the web project's root directory
    • Running the coalesce npm script (Vue) or gulp task (Knockout) in the Task Runner Explorer

You're now at a point where you can start creating your own pages!

Building Pages & Features

Lets say we've created a model called Person as follows, and we've ran code generation with dotnet coalesce:

c#
namespace MyApplication.Data.Models 
+{
+    public class Person
+    {
+        public int PersonId { get; set; }
+        public string Name { get; set; }
+        public DateTimeOffset? BirthDate { get; set; }
+    }
+}

We can create a details page for a Person by creating a Single File Component in MyApplication.Web/src/views/person-details.vue:

vue
<template>
+  <dl>
+    <dt>Name</dt>
+    <dd>
+      <c-display :model="person" for="name" />
+    </dd>
+
+    <dt>Date of Birth</dt>
+    <dd>
+      <c-display :model="person" for="birthDate" format="M/d/yyyy" />
+    </dd>
+  </dl>
+</template>
+
+<script setup lang="ts"> 
+import { PersonViewModel } from "@/viewmodels.g";
+
+const props = defineProps<{ id: number }>();
+const person = new PersonViewModel();
+
+person.$load(props.id);
+</script>

Note

In the code above, c-display is a component that comes from the Vuetify Components for Coalesce.

For simple property types like string and number you can always use simple template interpolation syntax, but for more complex properties like dates, c-display is handy to use because it includes features like built-in date formatting.

We then need to add route to this new view. In MyApplication.Web/src/router.ts, add a new item to the routes array:

ts
// In the \`routes\` array, add the following item:
+{
+  path: '/person/:id',
+  name: 'person-details',
+  component: () => import('@/views/person-details.vue'),
+  props: route => ({ id: +route.params.id }),
+},

With these pieces in place, we now have a functioning page that will display details about a person. We can start up the application (or, if it was already running, refresh the page) and navigate to /person/1 (assuming a person with ID 1 exists - if not, navigate to /admin/Person and create one).

From this point, you can start adding more fields, more features, and more flair to the page. Check out all the other documentation in the sidebar to see what else Coalesce has to offer, including the Vue Overview.

`,24),t=[l];function p(c,r,i,d,y,D){return a(),e("div",null,t)}const h=s(o,[["render",p]]);export{C as __pageData,h as default}; diff --git a/assets/stacks_vue_getting-started.md.2JJlpDJm.lean.js b/assets/stacks_vue_getting-started.md.2JJlpDJm.lean.js new file mode 100644 index 000000000..7e816b03f --- /dev/null +++ b/assets/stacks_vue_getting-started.md.2JJlpDJm.lean.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as e,R as n}from"./chunks/framework.g9eZ-ZSs.js";const C=JSON.parse('{"title":"Getting Started","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/getting-started.md","filePath":"stacks/vue/getting-started.md"}'),o={name:"stacks/vue/getting-started.md"},l=n("",24),t=[l];function p(c,r,i,d,y,D){return a(),e("div",null,t)}const h=s(o,[["render",p]]);export{C as __pageData,h as default}; diff --git a/assets/stacks_vue_layers_api-clients.md.BwZLF0NI.js b/assets/stacks_vue_layers_api-clients.md.BwZLF0NI.js new file mode 100644 index 000000000..6958d2270 --- /dev/null +++ b/assets/stacks_vue_layers_api-clients.md.BwZLF0NI.js @@ -0,0 +1,52 @@ +import{_ as t,D as l,o as r,c,I as n,R as o,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const G=JSON.parse('{"title":"Vue API Client Layer","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/layers/api-clients.md","filePath":"stacks/vue/layers/api-clients.md"}'),p={name:"stacks/vue/layers/api-clients.md"},i=o(`

Vue API Client Layer

The API client layer, generated as api-clients.g.ts, exports a class for each API controller that was generated for your data model. These classes are stateless and provide one method for each API endpoint. This includes both the standard set of endpoints created for Entity Models and Custom DTOs, as well as any custom Methods on the aforementioned types, as well as any methods on your Services.

The API clients provided by Coalesce are based on axios. All API clients used a shared axios instance, exported from coalesce-vue as AxiosClient. This instance can be used to configure all HTTP requests made by Coalesce, including things like attaching interceptors to modify the requests being made, or configuring defaults.

As with all the layers, the source code of coalesce-vue is also a great supplement to this documentation.

Concepts

API Client

A class, generated for each controller-backed type in your data model as <ModelName>ApiClient and exported from api-clients.g.ts containing one method for each API endpoint.

Each method on the API client takes in the regular parameters of the method as you would expect, as well as an optional AxiosRequestConfig parameter at the end that can be used to provide additional configuration for the single request, if needed.

For the methods that correspond to the standard set of CRUD endpoints that Coalesce provides (get, list, count, save, delete), an additional parameter parameters is available that accepts the set of Standard Parameters appropriate for the endpoint.

Each method returns a Promise<AxiosResponse<TApiResult>> where TApiResult is either ItemResult, ItemResult<T>, or ListResult<T>, depending on the return type of the API endpoint. AxiosResponse is the response object from axios, containing the TApiResult in its data property, as well as other properties like headers. The returned type T is automatically converted into valid Model implementations for you.

API Callers/API States

A stateful function for invoking an API endpoint, created with the $makeCaller function on an API Client. API Callers provide a wide array of functionality that is useful for working with API endpoints that are utilized by a user interface.

Because they are such an integral part of the overall picture of coalesce-vue, they have their own section below where they are explained in much greater detail.

API Callers

API Callers (typed with the name ApiState in coalesce-vue, sometimes also referred to as "loaders" or "invokers") are stateful functions for invoking an API endpoint, created with the $makeCaller function on an API Client. A summary of features:

Endpoint Invocation

Each API Caller is itself a function, so it can be invoked to trigger an API request to the server.

State management

API Callers contain properties about the last request made, including things like wasSuccessful, isLoading, result, and more.

Concurrency Management

Using setConcurrency(mode), you can configure how each individual caller handles what happens when multiple requests are made simultaneously

Argument Binding

API Callers can be created so that they have an args object that can be bound to, using .invokeWithArgs() to make a request using those arguments as the API endpoint's parameters. The API Callers created for the ViewModel Layer are all created this way.

Creating and Invoking an API Caller

API Callers can be created with the $makeCaller method of an API Client. The way in which it was created affects how it is invoked, as the parameters that the caller accepts are defined when it is created.

TIP

During typical development, it is unlikely that you'll need to make a custom API Caller - the ones created for you on the generated ViewModel Layer will usually suffice. However, creating your own can allow for some more advanced functionality.

Some examples:

ts
// Preamble for all the examples below:
+import { PersonApiClient } from '@/api-clients.g';
+const client = new PersonApiClient;

A caller that takes no additional parameters:

ts
const caller = client.$makeCaller(
+    "item", 
+    c => c.namesStartingWith("A")
+);
+
+await caller();
+console.log(caller.result)

A caller that takes custom parameters:

ts
const caller = client.$makeCaller(
+    methods => methods.namesStartingWith, 
+    (c, str: string) => c.namesStartingWith(str)
+);
+
+await caller("Rob");
+console.log(caller.result)

A caller that has an args object that can be bound to. This is how the generated API Callers in the ViewModel Layer are created:

ts
const caller = client.$makeCaller("item", 
+    // The parameter-based version is always required, even if it won't be used.
+    (c, str: string) => c.namesStartingWith(str),
+    // A function which creates a blank instance of the args object.
+    // All props should be initialized (i.e. not undefined) to work with Vue's reactivity.
+    () => ({str: null as string | null, }),
+    // The function that accepts the args object and uses it:
+    (c, args) => c.namesStartingWith(args.str)
+);
+
+caller.args.str = "Su";
+await caller.invokeWithArgs();
+console.log(caller.result)

A caller that performs multiple async operations:

ts
const deleteFirstNameStartingWith = client.$makeCaller(
+    "item",
+    async (c, str: string) => {
+        const namesResult = await c.namesStartingWith(str)
+        return await c.deletePersonByName(namesResult.data.object[0])
+    }
+);
+
+await caller("Rob");
+console.log(caller.result)

The first parameter, resultType, can either be one of "item" or "list", indicating whether the method returns a ItemResult or ListResult (examples #1 and #3 above). It can also be a function which accepts the set of method metadata for the API Client and which returns the specific method metadata (example #2 above), or it can be a direct reference to a specific method metadata object.

Properties

The following state properties can be found on API Caller instances. These properties are useful for binding to in a user interface to display errors, results, or indicators of progress.

All Callers

`,40),d=e("p",null,"True if there is currently a request pending for the API Caller.",-1),h=e("p",null,"A boolean indicating if the last request made was successful, or null if either no request has been made yet, or if a request has been made but has not yet completed.",-1),u=e("p",null,"An error message from the last request, if any. Will be set to null upon successful completion of a request.",-1),D=e("p",null,[s("True if "),e("code",null,"result"),s(" is non-null. This prop is useful in performance-critical scenarios where checking "),e("code",null,"result"),s(" directly will cause an overabundance of re-renders in high-churn scenarios.")],-1),y=e("p",null,[s("Holds an object for the arguments of the function, and will be used if the caller is invoked with its "),e("code",null,"invokeWithArgs()"),s(" method. Useful for binding the arguments of a caller to inputs in a user interface.")],-1),m=e("p",null,"Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.",-1),C=e("p",null,[s("Returns the URL for the method's HTTP endpoint. Any parameters are sourced from the "),e("code",null,"args"),s(" object. Useful for binding file-returning HTTP GET methods directly to "),e("code",null,"image"),s(" or "),e("code",null,"video"),s(" HTML elements.")],-1),f=e("p",null,"Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.",-1),g=e("br",null,null,-1),b=e("h4",{id:"itemresult-based-callers",tabindex:"-1"},[s("ItemResult-based Callers "),e("a",{class:"header-anchor",href:"#itemresult-based-callers","aria-label":'Permalink to "ItemResult-based Callers"'},"​")],-1),v=e("p",null,"The principal data returned by the previous request. Will be set to null if the last response received returned no data (e.g. if the response was an error response)",-1),w=e("p",null,[s("Any validation issues returned by the previous request. This is never populated automatically by Coalesce, and is therefore is only used if you have written custom code to populate it in your "),e("a",{href:"/Coalesce/modeling/model-components/behaviors.html"},"Behaviors"),s(" or "),e("a",{href:"/Coalesce/modeling/model-components/methods.html"},"Methods"),s(".")],-1),A=e("br",null,null,-1),q=e("h4",{id:"listresult-based-callers",tabindex:"-1"},[s("ListResult-based Callers "),e("a",{class:"header-anchor",href:"#listresult-based-callers","aria-label":'Permalink to "ListResult-based Callers"'},"​")],-1),k=e("p",null,"The principal data returned by the previous request. Will be set to null if the last response received returned no data (e.g. if the response was an error response).",-1),_=e("p",null,"Properties which contain the pagination information returned by the previous request.",-1),P=e("h3",{id:"concurrency-mode",tabindex:"-1"},[s("Concurrency Mode "),e("a",{class:"header-anchor",href:"#concurrency-mode","aria-label":'Permalink to "Concurrency Mode"'},"​")],-1),E=o('

API callers have a setConcurrency method that allows you to customize how they behave when additional invocations are performed when there is already a request pending. There are four options available, with "disallow" being the default:

"disallow"

The default behavior - simply throws an error for any secondary invocations.

Note

Having "disallow" as the default prevents the unexpected behavior that can happen in a number of ways with the other modes:

  • For requests that are performing data-mutating actions on the server, all other concurrency modes could lead to an unexpected end state of the data due to requests either being abandoned, cancelled, or potentially happening out-of-order.
  • Throwing errors for multiple concurrent requests quickly surfaces issues during development where concurrent requests are not being correctly guarded against in a user interface - e.g. not disabling a "Save" or "Submit" button while the request is pending, which would otherwise lead to double-posts.
"debounce"

When a secondary invocation is performed, enqueue it after the current pending invocation completes.

If additional invocations are performed while there is already an invocation enqueued and waiting, the already-enqueued invocation is abandoned and replaced by the most recent invocation attempt. The promise of the abandoned invocation will be resolved with undefined (it is NOT rejected).

"cancel"

When a secondary invocation is performed, cancel the current pending invocation.

This completely aborts the request, propagating all the way back to the server where cancellation can be observed with HttpContext.RequestAborted. The promise of the cancelled invocation will be resolved with undefined (it is NOT rejected).

"allow"

When a secondary invocation is performed, always continue normally, sending the request to the server.

The state of the properties on the caller at any time will reflect the most recent response received from the server, which is never guaranteed to correlate with the most recent request made to the server - that is, requests are not guaranteed to complete in the order they were made. In particular, the isLoading property will be false after the first response comes back, even if the second response has not yet been received.

WARNING

For the reasons outlined above, it is generally not recommended to use "allow" unless you fully understand the drawbacks. This mode mirrors the legacy behavior of the Knockout stack for Coalesce.

Response Caching

Response caching on API Callers is a feature that will save API responses to persistent storage (sessionStorage or localStorage). The next time a matching request is made, the result property of the API Caller will be populated with that saved response, allowing for a faster time to interactivity and reduced repaints and shifting of elements as initial data loads after a page navigation. It does not prevent any HTTP requests from being made, and does not affect the Promise returned from invoke or invokeWithArgs.

Common use cases include:

  • Site-wide status or alert messages
  • Server-provided configuration
  • Dashboard data, like statistics or graphs

When a cached response is loaded, result is populated with that response's data, wasSuccessful and hasResult are set to true, and onFulfilled callbacks are invoked.

',19),T=o(`

Enables response caching on the API Caller. Only HTTP GET methods are supported, and file-returning methods are not supported. Call with false to disable caching after it was previously enabled. The available options are as follows:

ts
export type ResponseCachingConfiguration = {
+  /** Function that will determine the cache key used for a particular request.
+   * Return a falsy value to prevent caching. The default key is the request URL.
+   */
+  key?: (
+    req: AxiosRequestConfig,
+    defaultKey: string
+  ) => string | null | undefined;
+
+  /** The maximum age of a cached response. If null, the entry will not expire. Default 1 hour.
+   *
+   * The smallest of the current configured max age and the max age that was set at the time of the cached response is used. */
+  maxAgeSeconds?: number | null;
+
+  /** The Storage (default \`sessionStorage\`) that will hold cached responses. */
+  storage?: Storage;
+};

Other Methods

API Callers have a few other methods available as well:

`,4),I=e("p",null,[s("Manually cancel the current request. The promise of the cancelled invocation will be resolved with "),e("code",null,"undefined"),s(" (it is NOT rejected). If using concurrency mode "),e("code",null,'"allow"'),s(", only the most recent invocation is cancelled.")],-1),F=e("p",null,[s("Add a callback to the caller to be invoked when a success response is received from the server. If a promise is returned, this promise will be awaited and will delay the setting of the "),e("code",null,"isLoading"),s(" prop to "),e("code",null,"false"),s(" until it completes.")],-1),x=e("p",null,[s("Add a callback to the caller to be invoked when a failure response is received from the server. If a promise is returned, this promise will be awaited and will delay the setting of the "),e("code",null,"isLoading"),s(" prop to "),e("code",null,"false"),s(" until it completes.")],-1),R=e("p",null,[s("The invoke function is a reference from the caller to itself. In other words, "),e("code",null,"caller.invoke === caller"),s(". This exists to mirror the syntax of the Knockout generated method classes.")],-1),S=e("p",null,[s("If called a parameter, that parameter will be used as the args object. Otherwise, "),e("code",null,"caller.args"),s(" will be used.")],-1),L=e("p",null,"Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.",-1),j=e("p",null,[s("If the method returns a file, this method will return an "),e("a",{href:"https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL",target:"_blank",rel:"noreferrer"},"Object URL"),s(" representing the value of the "),e("code",null,"result"),s(" prop.")],-1),B=e("p",null,[s("Accepts a "),e("code",null,"Vue"),s(" instance in order to manage the lifecycle of the URL, since object URLs must be manually released to avoid memory leaks. When the provided Vue component is destroyed, the object URL will be destroyed. If called inside the component template, the Vue instance can be acquired automatically.")],-1),V=e("p",null,"Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.",-1);function W(N,M,O,U,$,H){const a=l("Prop");return r(),c("div",null,[i,n(a,{def:"isLoading: boolean",lang:"ts"}),d,n(a,{def:"wasSuccessful: boolean | null",lang:"ts"}),h,n(a,{def:"message: string | null",lang:"ts"}),u,n(a,{def:"hasResult: boolean",lang:"ts"}),D,n(a,{def:"args: {}",lang:"ts"}),y,m,n(a,{def:"get url(): string",lang:"ts"}),C,f,g,b,n(a,{def:"result: T | null",lang:"ts",id:"member-result-item"}),v,n(a,{def:"validationIssues: ValidationIssue[] | null",lang:"ts"}),w,A,q,n(a,{def:"result: Array | null",lang:"ts",id:"member-result-list"}),k,n(a,{def:"page, pageSize, pageCount, totalCount: number | null",lang:"ts",id:"members-pagination"}),_,P,n(a,{def:"setConcurrency(mode: 'disallow' | 'debounce' | 'cancel' | 'allow')",lang:"ts"}),E,n(a,{def:"useResponseCaching(configuration?: ResponseCachingConfiguration | false)",lang:"ts"}),T,n(a,{def:"cancel(): void",lang:"ts"}),I,n(a,{def:"onFulfilled((state: TInvoker) => void | Promise): void",lang:"ts"}),F,n(a,{def:"onRejected((state: TInvoker) => void | Promise): void",lang:"ts"}),x,n(a,{def:"invoke(...args: TArgs)",lang:"ts"}),R,n(a,{def:"invokeWithArgs(args?: {})",lang:"ts"}),S,L,n(a,{def:"getResultObjectUrl(vue?: Vue): string | undefined",lang:"ts"}),j,B,V])}const K=t(p,[["render",W]]);export{G as __pageData,K as default}; diff --git a/assets/stacks_vue_layers_api-clients.md.BwZLF0NI.lean.js b/assets/stacks_vue_layers_api-clients.md.BwZLF0NI.lean.js new file mode 100644 index 000000000..28eebc47b --- /dev/null +++ b/assets/stacks_vue_layers_api-clients.md.BwZLF0NI.lean.js @@ -0,0 +1 @@ +import{_ as t,D as l,o as r,c,I as n,R as o,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const G=JSON.parse('{"title":"Vue API Client Layer","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/layers/api-clients.md","filePath":"stacks/vue/layers/api-clients.md"}'),p={name:"stacks/vue/layers/api-clients.md"},i=o("",40),d=e("p",null,"True if there is currently a request pending for the API Caller.",-1),h=e("p",null,"A boolean indicating if the last request made was successful, or null if either no request has been made yet, or if a request has been made but has not yet completed.",-1),u=e("p",null,"An error message from the last request, if any. Will be set to null upon successful completion of a request.",-1),D=e("p",null,[s("True if "),e("code",null,"result"),s(" is non-null. This prop is useful in performance-critical scenarios where checking "),e("code",null,"result"),s(" directly will cause an overabundance of re-renders in high-churn scenarios.")],-1),y=e("p",null,[s("Holds an object for the arguments of the function, and will be used if the caller is invoked with its "),e("code",null,"invokeWithArgs()"),s(" method. Useful for binding the arguments of a caller to inputs in a user interface.")],-1),m=e("p",null,"Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.",-1),C=e("p",null,[s("Returns the URL for the method's HTTP endpoint. Any parameters are sourced from the "),e("code",null,"args"),s(" object. Useful for binding file-returning HTTP GET methods directly to "),e("code",null,"image"),s(" or "),e("code",null,"video"),s(" HTML elements.")],-1),f=e("p",null,"Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.",-1),g=e("br",null,null,-1),b=e("h4",{id:"itemresult-based-callers",tabindex:"-1"},[s("ItemResult-based Callers "),e("a",{class:"header-anchor",href:"#itemresult-based-callers","aria-label":'Permalink to "ItemResult-based Callers"'},"​")],-1),v=e("p",null,"The principal data returned by the previous request. Will be set to null if the last response received returned no data (e.g. if the response was an error response)",-1),w=e("p",null,[s("Any validation issues returned by the previous request. This is never populated automatically by Coalesce, and is therefore is only used if you have written custom code to populate it in your "),e("a",{href:"/Coalesce/modeling/model-components/behaviors.html"},"Behaviors"),s(" or "),e("a",{href:"/Coalesce/modeling/model-components/methods.html"},"Methods"),s(".")],-1),A=e("br",null,null,-1),q=e("h4",{id:"listresult-based-callers",tabindex:"-1"},[s("ListResult-based Callers "),e("a",{class:"header-anchor",href:"#listresult-based-callers","aria-label":'Permalink to "ListResult-based Callers"'},"​")],-1),k=e("p",null,"The principal data returned by the previous request. Will be set to null if the last response received returned no data (e.g. if the response was an error response).",-1),_=e("p",null,"Properties which contain the pagination information returned by the previous request.",-1),P=e("h3",{id:"concurrency-mode",tabindex:"-1"},[s("Concurrency Mode "),e("a",{class:"header-anchor",href:"#concurrency-mode","aria-label":'Permalink to "Concurrency Mode"'},"​")],-1),E=o("",19),T=o("",4),I=e("p",null,[s("Manually cancel the current request. The promise of the cancelled invocation will be resolved with "),e("code",null,"undefined"),s(" (it is NOT rejected). If using concurrency mode "),e("code",null,'"allow"'),s(", only the most recent invocation is cancelled.")],-1),F=e("p",null,[s("Add a callback to the caller to be invoked when a success response is received from the server. If a promise is returned, this promise will be awaited and will delay the setting of the "),e("code",null,"isLoading"),s(" prop to "),e("code",null,"false"),s(" until it completes.")],-1),x=e("p",null,[s("Add a callback to the caller to be invoked when a failure response is received from the server. If a promise is returned, this promise will be awaited and will delay the setting of the "),e("code",null,"isLoading"),s(" prop to "),e("code",null,"false"),s(" until it completes.")],-1),R=e("p",null,[s("The invoke function is a reference from the caller to itself. In other words, "),e("code",null,"caller.invoke === caller"),s(". This exists to mirror the syntax of the Knockout generated method classes.")],-1),S=e("p",null,[s("If called a parameter, that parameter will be used as the args object. Otherwise, "),e("code",null,"caller.args"),s(" will be used.")],-1),L=e("p",null,"Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.",-1),j=e("p",null,[s("If the method returns a file, this method will return an "),e("a",{href:"https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL",target:"_blank",rel:"noreferrer"},"Object URL"),s(" representing the value of the "),e("code",null,"result"),s(" prop.")],-1),B=e("p",null,[s("Accepts a "),e("code",null,"Vue"),s(" instance in order to manage the lifecycle of the URL, since object URLs must be manually released to avoid memory leaks. When the provided Vue component is destroyed, the object URL will be destroyed. If called inside the component template, the Vue instance can be acquired automatically.")],-1),V=e("p",null,"Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.",-1);function W(N,M,O,U,$,H){const a=l("Prop");return r(),c("div",null,[i,n(a,{def:"isLoading: boolean",lang:"ts"}),d,n(a,{def:"wasSuccessful: boolean | null",lang:"ts"}),h,n(a,{def:"message: string | null",lang:"ts"}),u,n(a,{def:"hasResult: boolean",lang:"ts"}),D,n(a,{def:"args: {}",lang:"ts"}),y,m,n(a,{def:"get url(): string",lang:"ts"}),C,f,g,b,n(a,{def:"result: T | null",lang:"ts",id:"member-result-item"}),v,n(a,{def:"validationIssues: ValidationIssue[] | null",lang:"ts"}),w,A,q,n(a,{def:"result: Array | null",lang:"ts",id:"member-result-list"}),k,n(a,{def:"page, pageSize, pageCount, totalCount: number | null",lang:"ts",id:"members-pagination"}),_,P,n(a,{def:"setConcurrency(mode: 'disallow' | 'debounce' | 'cancel' | 'allow')",lang:"ts"}),E,n(a,{def:"useResponseCaching(configuration?: ResponseCachingConfiguration | false)",lang:"ts"}),T,n(a,{def:"cancel(): void",lang:"ts"}),I,n(a,{def:"onFulfilled((state: TInvoker) => void | Promise): void",lang:"ts"}),F,n(a,{def:"onRejected((state: TInvoker) => void | Promise): void",lang:"ts"}),x,n(a,{def:"invoke(...args: TArgs)",lang:"ts"}),R,n(a,{def:"invokeWithArgs(args?: {})",lang:"ts"}),S,L,n(a,{def:"getResultObjectUrl(vue?: Vue): string | undefined",lang:"ts"}),j,B,V])}const K=t(p,[["render",W]]);export{G as __pageData,K as default}; diff --git a/assets/stacks_vue_layers_metadata.md.m2bw93Lp.js b/assets/stacks_vue_layers_metadata.md.m2bw93Lp.js new file mode 100644 index 000000000..70f6e6862 --- /dev/null +++ b/assets/stacks_vue_layers_metadata.md.m2bw93Lp.js @@ -0,0 +1 @@ +import{_ as o,D as r,o as s,c as d,I as t,R as e}from"./chunks/framework.g9eZ-ZSs.js";const v=JSON.parse('{"title":"Vue Metadata Layer","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/layers/metadata.md","filePath":"stacks/vue/layers/metadata.md"}'),n={name:"stacks/vue/layers/metadata.md"},l=e('

Vue Metadata Layer

The metadata layer, generated as metadata.g.ts, contains information about the types, properties, methods, and other components of your data model. Because Vue applications are typically compiled into a set of static assets, it is necessary for the frontend code to have a representation of your data model as an analog to the ReflectionRepository that is available at runtime in your .NET app.

Concepts

The following is a non-exhaustive list of the general concepts used by the metadata layer. The source code of coalesce-vue provides the most exhaustive set of documentation about the metadata layer:

Metadata

All objects in the metadata layer that represent any kind of metadata have, at the very least, a name, the name of the metadata element in code (type names, property names, parameter names, etc). and a displayName, the human-readable form of the name that is suitable for presentation when needed. Names follow the casing convention of their corresponding language elements - types are PascalCased, while other things like properties, methods, and parameters are camelCased.

Type

All custom types exposed by your application's data model will have a Type metadata object generated. This includes both C# classes, and C# enums. Class types include model (for Entity Models and Custom DTOs) and object (for External Types).

Value

In the metadata layer, a Value is the usage of a type. This could be any type - strings, numbers, enums, classes, or even void. Values can be found in the collection of an object's properties, a method's parameters or return value, or as a data source's parameters.

All values have the following properties:

',11),c=e("

Type could be a language primitive like string or number, a non-primitive JavaScript type (date, file), or in the case of a custom Type, the type kind of that type (model, enum, object). For custom types, an additional property typeDef will refer to the Type metadata for that type.

",1),i=e('

Role represents what purpose the value serves in a relational model. Either value (the default - no relational role), primaryKey, foreignKey, referenceNavigation, or collectionNavigation.

Property

A Property is a more refined Value that contains a number of additional fields based on the role of the property.

Domain

The type of the default export of the generated metadata. Serves as a single root from which all other metadata can be accessed. Contains fields types, enums, and services as organizing structures for the different kinds of custom types.

',5);function p(h,m,u,y,f,_){const a=r("Prop");return s(),d("div",null,[l,t(a,{def:"type: TypeDiscriminator",lang:"ts"}),c,t(a,{def:"role: ValueRole",lang:"ts"}),i])}const T=o(n,[["render",p]]);export{v as __pageData,T as default}; diff --git a/assets/stacks_vue_layers_metadata.md.m2bw93Lp.lean.js b/assets/stacks_vue_layers_metadata.md.m2bw93Lp.lean.js new file mode 100644 index 000000000..80f0e3383 --- /dev/null +++ b/assets/stacks_vue_layers_metadata.md.m2bw93Lp.lean.js @@ -0,0 +1 @@ +import{_ as o,D as r,o as s,c as d,I as t,R as e}from"./chunks/framework.g9eZ-ZSs.js";const v=JSON.parse('{"title":"Vue Metadata Layer","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/layers/metadata.md","filePath":"stacks/vue/layers/metadata.md"}'),n={name:"stacks/vue/layers/metadata.md"},l=e("",11),c=e("",1),i=e("",5);function p(h,m,u,y,f,_){const a=r("Prop");return s(),d("div",null,[l,t(a,{def:"type: TypeDiscriminator",lang:"ts"}),c,t(a,{def:"role: ValueRole",lang:"ts"}),i])}const T=o(n,[["render",p]]);export{v as __pageData,T as default}; diff --git a/assets/stacks_vue_layers_models.md.hU96gV_Y.js b/assets/stacks_vue_layers_models.md.hU96gV_Y.js new file mode 100644 index 000000000..167431d19 --- /dev/null +++ b/assets/stacks_vue_layers_models.md.hU96gV_Y.js @@ -0,0 +1,77 @@ +import{_ as p,D as r,o as c,c as i,I as a,w as n,R as t,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const R=JSON.parse('{"title":"Vue Model Layer","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/layers/models.md","filePath":"stacks/vue/layers/models.md"}'),d={name:"stacks/vue/layers/models.md"},D=t(`

Vue Model Layer

The model layer, generated as models.g.ts, contains a set of TypeScript interfaces that represent each client-exposed type in your data model. Each interface contains all the Properties of that type, as well as a $metadata property that references the metadata object for that type. Enums and Data Sources are also represented in the model layer.

The model layer also includes a TypeScript class for each type that can be used to easily instantiate a valid implementation of its corresponding interface. However, it is not necessary for the classes to be used, and all parts of Coalesce that interact with the model layer don't perform any instanceof checks against models - the $metadata property is used to determine type identity.

Concepts

The model layer is fairly simple - the only main concept it introduces on top of the Metadata Layer is the notion of interfaces and enums that mirror the C# types in your data model. As with the Metadata Layer, the source code of coalesce-vue is a great documentation supplement to this page.

Model

An interface describing an instance of a class type from your application's data model. All Model interfaces contain members for all the Properties of that type, as well as a $metadata property that references the metadata object for that type.

DataSource

A class-based representation of a Data Source containing properties for any of the Custom Parameters of the data source, as well as a $metadata property that references the metadata object for the data source.

Data sources are generated as concrete classes in a namespace named DataSources that is nested inside a namespace named after their parent model type. For example:

ts
import { Person } from '@/models.g'
+
+const dataSource = new Person.DataSources.NamesStartingWith;
+dataSource.startsWith = "A";
+// Provide the dataSource to an API Client or a ViewModel...

Model Functions

The following functions exported from coalesce-vue can be used with your models:

`,13),y=t('

Binds property key of obj to query string parameter queryKey. When the object's value changes, the query string will be updated using vue-router. When the query string changes, the object's value will be updated.

The query string will be updated using either router.push or router.replace depending on the value of parameter mode.

If the query string contains a value when this is called, the object will be updated with that value immediately.

If the object being bound to has $metadata, information from that metadata will be used to serialize and parse values to and from the query string. Otherwise, String(value) will be used to serialize the value, and the parse parameter (if provided) will be used to parse the value from the query string.

',4),u=e("div",{class:"language-ts"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"ts"),e("pre",{class:"shiki dark-plus vp-code"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}},"import"),e("span",{style:{color:"#D4D4D4"}}," { "),e("span",{style:{color:"#9CDCFE"}},"bindToQueryString"),e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"from"),e("span",{style:{color:"#CE9178"}}," 'coalesce-vue'"),e("span",{style:{color:"#D4D4D4"}},";")]),s(` +`),e("span",{class:"line"}),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#6A9955"}},"// In the 'created' Vue lifecycle hook on a component:")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}},"created"),e("span",{style:{color:"#D4D4D4"}},"() {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#6A9955"}}," // Bind pagination information to the query string:")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," bindToQueryString"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"listViewModel"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"$params"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'pageSize'"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'pageSize'"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#9CDCFE"}},"v"),e("span",{style:{color:"#569CD6"}}," =>"),e("span",{style:{color:"#D4D4D4"}}," +"),e("span",{style:{color:"#9CDCFE"}},"v"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"}),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#6A9955"}}," // Assuming the component has an 'activeTab' data member:")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," bindToQueryString"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'activeTab'"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}},"}")])])])],-1),h=e("div",{class:"language-ts"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"ts"),e("pre",{class:"shiki dark-plus vp-code"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}},"import"),e("span",{style:{color:"#D4D4D4"}}," { "),e("span",{style:{color:"#9CDCFE"}},"useBindToQueryString"),e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"from"),e("span",{style:{color:"#CE9178"}}," 'coalesce-vue'"),e("span",{style:{color:"#D4D4D4"}},";")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}},"setup"),e("span",{style:{color:"#D4D4D4"}},"() {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#6A9955"}}," // Bind pagination information to the query string:")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#569CD6"}}," const"),e("span",{style:{color:"#4FC1FF"}}," list"),e("span",{style:{color:"#D4D4D4"}}," = "),e("span",{style:{color:"#569CD6"}},"new"),e("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),e("span",{style:{color:"#D4D4D4"}},"();")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," useBindToQueryString"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#9CDCFE"}},"list"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"$params"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'pageSize'"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'pageSize'"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#9CDCFE"}},"v"),e("span",{style:{color:"#569CD6"}}," =>"),e("span",{style:{color:"#D4D4D4"}}," +"),e("span",{style:{color:"#9CDCFE"}},"v"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"}),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#569CD6"}}," const"),e("span",{style:{color:"#4FC1FF"}}," activeTab"),e("span",{style:{color:"#D4D4D4"}}," = "),e("span",{style:{color:"#DCDCAA"}},"ref"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#CE9178"}},'"1"'),e("span",{style:{color:"#D4D4D4"}},")")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," useBindToQueryString"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#9CDCFE"}},"activeTab"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'value'"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'activeTab'"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}},"}")])])])],-1),m=e("p",null,[s("When "),e("code",null,"model"),s(" is created (i.e. its primary key becomes non-null), replace the current URL with one that includes uses primary key for the route parameter named by "),e("code",null,"routeParamName"),s(".")],-1),C=e("p",null,[s("The query string will not be kept when the route is changed unless "),e("code",null,"true"),s(" is given to "),e("code",null,"keepQuery"),s(".")],-1),f=e("div",{class:"language-ts"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"ts"),e("pre",{class:"shiki dark-plus vp-code"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}},"import"),e("span",{style:{color:"#D4D4D4"}}," { "),e("span",{style:{color:"#9CDCFE"}},"bindKeyToRouteOnCreate"),e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"from"),e("span",{style:{color:"#CE9178"}}," 'coalesce-vue'"),e("span",{style:{color:"#D4D4D4"}},";")]),s(` +`),e("span",{class:"line"}),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#6A9955"}},"// In the 'created' Vue lifecycle hook on a component:")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}},"created"),e("span",{style:{color:"#D4D4D4"}},"() {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}}," if"),e("span",{style:{color:"#D4D4D4"}}," ("),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"id"),e("span",{style:{color:"#D4D4D4"}},") {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#569CD6"}}," this"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"viewModel"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#DCDCAA"}},"$load"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"id"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"else"),e("span",{style:{color:"#D4D4D4"}}," {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," bindKeyToRouteOnCreate"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"viewModel"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}}," }")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}},"}")])])])],-1),b=e("div",{class:"language-ts"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"ts"),e("pre",{class:"shiki dark-plus vp-code"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}},"import"),e("span",{style:{color:"#D4D4D4"}}," { "),e("span",{style:{color:"#9CDCFE"}},"useBindKeyToRouteOnCreate"),e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"from"),e("span",{style:{color:"#CE9178"}}," 'coalesce-vue'"),e("span",{style:{color:"#D4D4D4"}},";")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}},"setup"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#9CDCFE"}},"props"),e("span",{style:{color:"#D4D4D4"}},") {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#569CD6"}}," const"),e("span",{style:{color:"#4FC1FF"}}," viewModel"),e("span",{style:{color:"#D4D4D4"}}," = "),e("span",{style:{color:"#569CD6"}},"new"),e("span",{style:{color:"#DCDCAA"}}," PersonViewModel"),e("span",{style:{color:"#D4D4D4"}},"();")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}}," if"),e("span",{style:{color:"#D4D4D4"}}," ("),e("span",{style:{color:"#9CDCFE"}},"props"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"id"),e("span",{style:{color:"#D4D4D4"}},") {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#9CDCFE"}}," viewModel"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#DCDCAA"}},"$load"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#9CDCFE"}},"props"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"id"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"else"),e("span",{style:{color:"#D4D4D4"}}," {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," useBindToQueryString"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#9CDCFE"}},"viewModel"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}}," }")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}},"}")])])])],-1),g=t('

Note

The route will be replaced directly via the HTML5 History API such that vue-router will not observe the change as an actual route change, preventing the current view from being recreated if a path-based key is being used on the application's <router-view> component.

Advanced Model Functions

The following functions exported from coalesce-vue can be used with your models.

Note

These functions are used to implement the higher-order layers in the Vue stack.

While you're absolutely free to use them in your own code and can rely on their interface and behavior to remain consistent, you will find that you seldom need to use them directly - that's why we've split them into their own section here in the documentation.

',4),_=t("

Given any JavaScript value value, convert it into a valid implementation of the value or type described by metadata.

For metadata describing a primitive or primitive-like value, the input will be parsed into a valid implementation of the correct JavaScript type. For example, for metadata that describes a boolean, a string "true" will return a boolean true, and ISO 8601 date strings will result in a JavaScript Date object.

For metadata describing a type, the input object will be mutated into a valid implementation of the appropriate model interface. Missing properties will be set to null, and any descendent properties of the provided object will be recursively processed with convertToModel.

If any values are encountered that are fundamentally incompatible with the requested type described by the metadata, an error will be thrown.

",4),v=e("p",null,[s("Performs the same operations as "),e("code",null,"convertToModel"),s(", except that any objects encountered will not be mutated - instead, a new object or array will always be created.")],-1),T=e("p",null,"Maps the input to a representation suitable for JSON serialization.",-1),A=e("p",null,[s("Will not serialize child objects or collections whose metadata includes "),e("code",null,"dontSerialize"),s(". Will only recurse to a maximum depth of 3.")],-1),w=e("p",null,[e("a",{id:"VueModelDisplayFunctions"})],-1),E=t('

Returns a string representing the model suitable for display in a user interface.

Uses the displayProp defined on the object's metadata. If no displayProp is defined, the object will be displayed as JSON. The display prop on a model can be defined in C# with [ListText].

See DisplayOptions for available options.

',3),k=e("p",null,"Returns a string representing the specified property of the given object suitable for display in a user interface.",-1),S=e("p",null,[s("The property can either be a string, representing one of the model's properties, or the actual "),e("code",null,"Property"),s(" metadata object of the property.")],-1),F=e("p",null,[s("See "),e("a",{href:"#displayoptions"},"DisplayOptions"),s(" for available options.")],-1),P=t(`

Returns a string representing the given value (described by the given metadata).

See DisplayOptions for available options.

DisplayOptions

The following options are available to functions in coalesce-vue that render a value or object for display:

ts
export interface DisplayOptions {
+  /** Date format options. One of:
+   * - A UTS#35 date format string (https://date-fns.org/docs/format)
+   * - An object with options for https://date-fns.org/docs/format or https://github.com/marnusw/date-fns-tz#format, including a string \`format\` for the format itself. If a \`timeZone\` option is provided per https://github.com/marnusw/date-fns-tz#format, the date being formatted will be converted to that timezone.
+   * - An object with options for https://date-fns.org/docs/formatDistance */
+  format?:
+    | string
+    | ({
+        /** A UTS#35 date format string (https://date-fns.org/docs/format) */
+        format: string;
+      } & Parameters<typeof format>[2])
+    | {
+        /** Format date with https://date-fns.org/docs/formatDistanceToNow */
+        distance: true;
+        /** Append/prepend \`'in'\` or \`'ago'\` if date is after/before now. Default \`true\`. */
+        addSuffix?: boolean;
+        /** Include detail smaller than one minute. Default \`false\`. */
+        includeSeconds?: boolean;
+      };
+
+  collection?: {
+    /** The maximum number of items to display individually.
+     * When there are more than this number of items, the count of items will be displayed instead.
+     * Default \`5\`.
+     * */
+    enumeratedItemsMax?: number;
+
+    /** The separator to place between enumerated items. Default \`', '\` */
+    enumeratedItemsSeparator?: string;
+  };
+}

Note

Dates rendered with the formatDistanceToNow function into a Vue component will not automatically be updated in realtime. If this is needed, you should use a strategy like using a key that you periodically update to force a re-render.

Time Zones

In Coalesce Vue, all DateTimeOffset-based properties, for both inputs and display-only contexts, are by default formatted into the user's computer's system time zone. This is largely just a consequence of how the JavaScript Date type works. However, this behavior can be overridden by configuring a global default timezone, or by providing a time zone name to individual usages.

Fields with a type of DateTime are agnostic to time zone and UTC offset and so are not subject to any of the following rules.

`,9),I=t('

Gets or sets the default time zone used by Coalesce. The time zone should be an IANA Time Zone Database name, e.g. "America/Los_Angeles".

The time zone provided here is used in the following ways:

  • It will be used as DisplayOptions.format.timeZone if no other value was provided for this option. This is used by functions modelDisplay, propDisplay, and valueDisplay, as well as the c-display component.
  • It will be used by c-datetime-picker, used to both interpret the user input and display the selected date. This can also be set on individual component usages via the timeZone prop.
  • It will be used when serializing DateTimeOffset fields into JSON DTOs, representing the ISO 8601 date string in the specified time zone rather than in the user's computer's system time zone.
',3),V=e("p",null,[s("Returns the current configured default time zone. Default is "),e("code",null,"null"),s(", falling back on the user's computer's system time zone.")],-1);function q(M,x,z,j,O,N){const o=r("Prop"),l=r("CodeTabs");return c(),i("div",null,[D,a(o,{def:`// Vue Options API +bindToQueryString(vue: Vue, obj: {}, key: string, queryKey: string = key, parse?: (v: any) => any, mode: 'push' | 'replace' = 'replace') +  +// Vue Composition API +useBindToQueryString(obj: {}, key: string, queryKey: string = key, parse?: (v: any) => any, mode: 'push' | 'replace' = 'replace')`,lang:"ts",idPrefix:"member-bindToQuery"}),y,a(l,{name:"vue"},{options:n(()=>[u]),setup:n(()=>[h]),_:1}),a(o,{def:`// Vue Options API +bindKeyToRouteOnCreate(vue: Vue, model: Model, routeParamName: string = 'id', keepQuery: boolean = false) +  +// Vue Composition API +useBindKeyToRouteOnCreate(model: Model, routeParamName: string = 'id', keepQuery: boolean = false)`,lang:"ts",idPrefix:"member-bindKey"}),m,C,a(l,{name:"vue"},{options:n(()=>[f]),setup:n(()=>[b]),_:1}),g,a(o,{def:"convertToModel(value: any, metadata: Value | ClassType): any",lang:"ts"}),_,a(o,{def:"mapToModel(value: any, metadata: Value | ClassType): any",lang:"ts"}),v,a(o,{def:"mapToDto(value: any, metadata: Value | ClassType): any",lang:"ts"}),T,A,w,a(o,{def:"modelDisplay(model: Model, options?: DisplayOptions): string",lang:"ts"}),E,a(o,{def:"propDisplay(model: Model, prop: Property | string, options?: DisplayOptions): string",lang:"ts"}),k,S,F,a(o,{def:"valueDisplay(value: any, metadata: Value, options?: DisplayOptions): string",lang:"ts"}),P,a(o,{def:"setDefaultTimeZone(timeZoneName: string | null): void",lang:"ts"}),I,a(o,{def:"getDefaultTimeZone(): string | null",lang:"ts"}),V])}const Q=p(d,[["render",q]]);export{R as __pageData,Q as default}; diff --git a/assets/stacks_vue_layers_models.md.hU96gV_Y.lean.js b/assets/stacks_vue_layers_models.md.hU96gV_Y.lean.js new file mode 100644 index 000000000..3340eac01 --- /dev/null +++ b/assets/stacks_vue_layers_models.md.hU96gV_Y.lean.js @@ -0,0 +1,43 @@ +import{_ as p,D as r,o as c,c as i,I as a,w as n,R as t,k as e,a as s}from"./chunks/framework.g9eZ-ZSs.js";const R=JSON.parse('{"title":"Vue Model Layer","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/layers/models.md","filePath":"stacks/vue/layers/models.md"}'),d={name:"stacks/vue/layers/models.md"},D=t("",13),y=t("",4),u=e("div",{class:"language-ts"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"ts"),e("pre",{class:"shiki dark-plus vp-code"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}},"import"),e("span",{style:{color:"#D4D4D4"}}," { "),e("span",{style:{color:"#9CDCFE"}},"bindToQueryString"),e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"from"),e("span",{style:{color:"#CE9178"}}," 'coalesce-vue'"),e("span",{style:{color:"#D4D4D4"}},";")]),s(` +`),e("span",{class:"line"}),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#6A9955"}},"// In the 'created' Vue lifecycle hook on a component:")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}},"created"),e("span",{style:{color:"#D4D4D4"}},"() {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#6A9955"}}," // Bind pagination information to the query string:")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," bindToQueryString"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"listViewModel"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"$params"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'pageSize'"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'pageSize'"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#9CDCFE"}},"v"),e("span",{style:{color:"#569CD6"}}," =>"),e("span",{style:{color:"#D4D4D4"}}," +"),e("span",{style:{color:"#9CDCFE"}},"v"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"}),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#6A9955"}}," // Assuming the component has an 'activeTab' data member:")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," bindToQueryString"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'activeTab'"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}},"}")])])])],-1),h=e("div",{class:"language-ts"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"ts"),e("pre",{class:"shiki dark-plus vp-code"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}},"import"),e("span",{style:{color:"#D4D4D4"}}," { "),e("span",{style:{color:"#9CDCFE"}},"useBindToQueryString"),e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"from"),e("span",{style:{color:"#CE9178"}}," 'coalesce-vue'"),e("span",{style:{color:"#D4D4D4"}},";")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}},"setup"),e("span",{style:{color:"#D4D4D4"}},"() {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#6A9955"}}," // Bind pagination information to the query string:")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#569CD6"}}," const"),e("span",{style:{color:"#4FC1FF"}}," list"),e("span",{style:{color:"#D4D4D4"}}," = "),e("span",{style:{color:"#569CD6"}},"new"),e("span",{style:{color:"#DCDCAA"}}," PersonListViewModel"),e("span",{style:{color:"#D4D4D4"}},"();")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," useBindToQueryString"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#9CDCFE"}},"list"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"$params"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'pageSize'"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'pageSize'"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#9CDCFE"}},"v"),e("span",{style:{color:"#569CD6"}}," =>"),e("span",{style:{color:"#D4D4D4"}}," +"),e("span",{style:{color:"#9CDCFE"}},"v"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"}),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#569CD6"}}," const"),e("span",{style:{color:"#4FC1FF"}}," activeTab"),e("span",{style:{color:"#D4D4D4"}}," = "),e("span",{style:{color:"#DCDCAA"}},"ref"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#CE9178"}},'"1"'),e("span",{style:{color:"#D4D4D4"}},")")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," useBindToQueryString"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#9CDCFE"}},"activeTab"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'value'"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#CE9178"}},"'activeTab'"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}},"}")])])])],-1),m=e("p",null,[s("When "),e("code",null,"model"),s(" is created (i.e. its primary key becomes non-null), replace the current URL with one that includes uses primary key for the route parameter named by "),e("code",null,"routeParamName"),s(".")],-1),C=e("p",null,[s("The query string will not be kept when the route is changed unless "),e("code",null,"true"),s(" is given to "),e("code",null,"keepQuery"),s(".")],-1),f=e("div",{class:"language-ts"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"ts"),e("pre",{class:"shiki dark-plus vp-code"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}},"import"),e("span",{style:{color:"#D4D4D4"}}," { "),e("span",{style:{color:"#9CDCFE"}},"bindKeyToRouteOnCreate"),e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"from"),e("span",{style:{color:"#CE9178"}}," 'coalesce-vue'"),e("span",{style:{color:"#D4D4D4"}},";")]),s(` +`),e("span",{class:"line"}),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#6A9955"}},"// In the 'created' Vue lifecycle hook on a component:")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}},"created"),e("span",{style:{color:"#D4D4D4"}},"() {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}}," if"),e("span",{style:{color:"#D4D4D4"}}," ("),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"id"),e("span",{style:{color:"#D4D4D4"}},") {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#569CD6"}}," this"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"viewModel"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#DCDCAA"}},"$load"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"id"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"else"),e("span",{style:{color:"#D4D4D4"}}," {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," bindKeyToRouteOnCreate"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},", "),e("span",{style:{color:"#569CD6"}},"this"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"viewModel"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}}," }")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}},"}")])])])],-1),b=e("div",{class:"language-ts"},[e("button",{title:"Copy Code",class:"copy"}),e("span",{class:"lang"},"ts"),e("pre",{class:"shiki dark-plus vp-code"},[e("code",null,[e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}},"import"),e("span",{style:{color:"#D4D4D4"}}," { "),e("span",{style:{color:"#9CDCFE"}},"useBindKeyToRouteOnCreate"),e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"from"),e("span",{style:{color:"#CE9178"}}," 'coalesce-vue'"),e("span",{style:{color:"#D4D4D4"}},";")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}},"setup"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#9CDCFE"}},"props"),e("span",{style:{color:"#D4D4D4"}},") {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#569CD6"}}," const"),e("span",{style:{color:"#4FC1FF"}}," viewModel"),e("span",{style:{color:"#D4D4D4"}}," = "),e("span",{style:{color:"#569CD6"}},"new"),e("span",{style:{color:"#DCDCAA"}}," PersonViewModel"),e("span",{style:{color:"#D4D4D4"}},"();")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#C586C0"}}," if"),e("span",{style:{color:"#D4D4D4"}}," ("),e("span",{style:{color:"#9CDCFE"}},"props"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"id"),e("span",{style:{color:"#D4D4D4"}},") {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#9CDCFE"}}," viewModel"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#DCDCAA"}},"$load"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#9CDCFE"}},"props"),e("span",{style:{color:"#D4D4D4"}},"."),e("span",{style:{color:"#9CDCFE"}},"id"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}}," } "),e("span",{style:{color:"#C586C0"}},"else"),e("span",{style:{color:"#D4D4D4"}}," {")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#DCDCAA"}}," useBindToQueryString"),e("span",{style:{color:"#D4D4D4"}},"("),e("span",{style:{color:"#9CDCFE"}},"viewModel"),e("span",{style:{color:"#D4D4D4"}},");")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}}," }")]),s(` +`),e("span",{class:"line"},[e("span",{style:{color:"#D4D4D4"}},"}")])])])],-1),g=t("",4),_=t("",4),v=e("p",null,[s("Performs the same operations as "),e("code",null,"convertToModel"),s(", except that any objects encountered will not be mutated - instead, a new object or array will always be created.")],-1),T=e("p",null,"Maps the input to a representation suitable for JSON serialization.",-1),A=e("p",null,[s("Will not serialize child objects or collections whose metadata includes "),e("code",null,"dontSerialize"),s(". Will only recurse to a maximum depth of 3.")],-1),w=e("p",null,[e("a",{id:"VueModelDisplayFunctions"})],-1),E=t("",3),k=e("p",null,"Returns a string representing the specified property of the given object suitable for display in a user interface.",-1),S=e("p",null,[s("The property can either be a string, representing one of the model's properties, or the actual "),e("code",null,"Property"),s(" metadata object of the property.")],-1),F=e("p",null,[s("See "),e("a",{href:"#displayoptions"},"DisplayOptions"),s(" for available options.")],-1),P=t("",9),I=t("",3),V=e("p",null,[s("Returns the current configured default time zone. Default is "),e("code",null,"null"),s(", falling back on the user's computer's system time zone.")],-1);function q(M,x,z,j,O,N){const o=r("Prop"),l=r("CodeTabs");return c(),i("div",null,[D,a(o,{def:`// Vue Options API +bindToQueryString(vue: Vue, obj: {}, key: string, queryKey: string = key, parse?: (v: any) => any, mode: 'push' | 'replace' = 'replace') +  +// Vue Composition API +useBindToQueryString(obj: {}, key: string, queryKey: string = key, parse?: (v: any) => any, mode: 'push' | 'replace' = 'replace')`,lang:"ts",idPrefix:"member-bindToQuery"}),y,a(l,{name:"vue"},{options:n(()=>[u]),setup:n(()=>[h]),_:1}),a(o,{def:`// Vue Options API +bindKeyToRouteOnCreate(vue: Vue, model: Model, routeParamName: string = 'id', keepQuery: boolean = false) +  +// Vue Composition API +useBindKeyToRouteOnCreate(model: Model, routeParamName: string = 'id', keepQuery: boolean = false)`,lang:"ts",idPrefix:"member-bindKey"}),m,C,a(l,{name:"vue"},{options:n(()=>[f]),setup:n(()=>[b]),_:1}),g,a(o,{def:"convertToModel(value: any, metadata: Value | ClassType): any",lang:"ts"}),_,a(o,{def:"mapToModel(value: any, metadata: Value | ClassType): any",lang:"ts"}),v,a(o,{def:"mapToDto(value: any, metadata: Value | ClassType): any",lang:"ts"}),T,A,w,a(o,{def:"modelDisplay(model: Model, options?: DisplayOptions): string",lang:"ts"}),E,a(o,{def:"propDisplay(model: Model, prop: Property | string, options?: DisplayOptions): string",lang:"ts"}),k,S,F,a(o,{def:"valueDisplay(value: any, metadata: Value, options?: DisplayOptions): string",lang:"ts"}),P,a(o,{def:"setDefaultTimeZone(timeZoneName: string | null): void",lang:"ts"}),I,a(o,{def:"getDefaultTimeZone(): string | null",lang:"ts"}),V])}const Q=p(d,[["render",q]]);export{R as __pageData,Q as default}; diff --git a/assets/stacks_vue_layers_viewmodels.md.y7kFb0eu.js b/assets/stacks_vue_layers_viewmodels.md.y7kFb0eu.js new file mode 100644 index 000000000..f3a4fb5b9 --- /dev/null +++ b/assets/stacks_vue_layers_viewmodels.md.y7kFb0eu.js @@ -0,0 +1,56 @@ +import{_ as n,D as l,o as r,c as i,I as o,R as s,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const Ie=JSON.parse('{"title":"Vue ViewModel Layer","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/layers/viewmodels.md","filePath":"stacks/vue/layers/viewmodels.md"}'),d={name:"stacks/vue/layers/viewmodels.md"},c=s('

Vue ViewModel Layer

The ViewModel layer, generated as viewmodels.g.ts, exports a ViewModel class for each API-backed type in your data model (Entity Models, Custom DTOs, and Services). It also exports a ListViewModel type for Entity Models and Custom DTOs.

These classes provide a wide array of functionality that is useful when interacting with your data model through a user interface. The generated ViewModels are the primary way that Coalesce is used when developing a Vue application.

ViewModels

The following members can be found on the generated Entity and Custom DTO ViewModels, exported from viewmodels.g.ts as <TypeName>ViewModel.

Model Data Properties

Each ViewModel class implements the corresponding interface from the Model Layer, meaning that the ViewModel has a data property for each Property on the model. Object-typed properties will be typed as the corresponding generated ViewModel.

Changing the value of a property will automatically flag that property as dirty. See Auto-save & Dirty Flags below for information on how property dirty flags are used.

There are a few special behaviors when assigning to different kinds of data properties on View Models as well:

Model Object Properties

  • If the object being assigned to the property is not a ViewModel instance, a new instance will be created automatically and used instead of the incoming object.
  • If the model property is a reference navigation, the corresponding foreign key property will automatically be set to the primary key of that object. If the incoming value was null, the foreign key will be set to null.
  • If deep auto-saves are enabled on the instance being assigned to, auto-save will be spread to the incoming object, and to all other objects reachable from that object.

Model Collection Properties

  • When assigning an entire array, any items in the array that are not a ViewModel instance will have an instance created for them.
  • The same rule goes for pushing items into the existing array for a model collection - a new ViewModel instance will be created and be used instead of the object(s) being pushed.

Foreign Key Properties

If the corresponding navigation property contains an object, and that object's primary key doesn't match the new foreign key value being assigned, the navigation property will be set to null.

Other Data Properties & Functions

',16),p=e("p",null,[a("The metadata object from the "),e("a",{href:"/Coalesce/stacks/vue/layers/metadata.html"},"Metadata Layer"),a(" layer for the type represented by the ViewModel.")],-1),h=e("p",null,"An immutable number that is unique among all ViewModel instances, regardless of type.",-1),u=e("p",null,[a("Useful for uniquely identifying instances with "),e("code",null,':key="vm.$stableId"'),a(" in a Vue component, especially for instances that lack a primary key.")],-1),m=e("p",null,"A getter/setter property that wraps the primary key of the model. Used to interact with the primary key of any ViewModel in a polymorphic way.",-1),f=e("p",null,"Returns a string representation of the object, or one of its properties if specified, suitable for display.",-1),y=e("p",null,[a("Creates a new instance of an item for the specified child model collection, adds it to that collection, and returns the item. If "),e("code",null,"initialDirtyData"),a(" is provided, it will be loaded into the new instance with "),e("code",null,"$loadDirtyData()"),a(".")],-1),g=e("h3",{id:"loading-parameters",tabindex:"-1"},[a("Loading & Parameters "),e("a",{class:"header-anchor",href:"#loading-parameters","aria-label":'Permalink to "Loading & Parameters"'},"​")],-1),v=s('

An API Caller for the /get endpoint. Accepts an optional id argument - if not provided, the ViewModel's $primaryKey is used instead. Uses the instance's $params object for the Standard Parameters.

',1),b=e("p",null,[a("An object containing the "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Standard Parameters"),a(" to be used for the "),e("code",null,"$load"),a(", "),e("code",null,"$save"),a(", "),e("code",null,"$bulkSave"),a(", and "),e("code",null,"$delete"),a(" API callers.")],-1),_=e("p",null,[a("Getter/setter wrapper around "),e("code",null,"$params.dataSource"),a(". Takes an instance of a "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html"},"Data Source"),a(" class "),e("a",{href:"/Coalesce/stacks/vue/layers/models.html"},"generated in the Model Layer"),a(".")],-1),w=e("p",null,[a("Getter/setter wrapper around "),e("code",null,"$params.includes"),a(". See "),e("a",{href:"/Coalesce/concepts/includes.html"},"Includes String"),a(" for more information.")],-1),D=e("p",null,"Loads data from the provided model into the current ViewModel, and then clears all dirty flags.",-1),C=e("p",null,"Data is loaded recursively into all related ViewModel instances, preserving existing instances whose primary keys match the incoming data.",-1),P=e("p",null,[a("If auto-save is enabled, only non-dirty properties are updated. This prevents user input that is pending a save from being overwritten by the response from an auto-save "),e("code",null,"/save"),a(" request.")],-1),A=e("p",null,[a("If "),e("code",null,"purgeUnsaved"),a(" is true, items without a primary key will be dropped from collection navigation properties. This is used by the "),e("code",null,"$load"),a(" caller in order to fully reset the object graph with the state from the server.")],-1),T=e("p",null,[a("Same as "),e("code",null,"$loadCleanData"),a(", but does not clear any existing dirty flags, nor does it clear any dirty flags that will be set while mutating the data properties of any ViewModel instance that gets loaded.")],-1),k=e("p",null,[a("Create a new instance of the ViewModel, loading the given initial data with "),e("code",null,"$loadDirtyData()"),a(" if provided.")],-1),$=e("h3",{id:"saving-and-deleting",tabindex:"-1"},[a("Saving and Deleting "),e("a",{class:"header-anchor",href:"#saving-and-deleting","aria-label":'Permalink to "Saving and Deleting"'},"​")],-1),S=s('

An API Caller for the /save endpoint. Uses the instance's $params object for the Standard Parameters. A save operation saves only properties on the model it is called on - for deep/bulk saves, see $bulkSave.

This caller is used for both manually-triggered saves in custom code and for auto-saves. If the Rules/Validation report any errors when the caller is invoked, an error will be thrown.

overrideProps can provide properties to save that override the data properties on the ViewModel instance. This allows for manually saving a change to a property without setting the property on the ViewModel instance into a dirty state. This makes it easier to handle some scenarios where changing the value of the property may put the UI into a logically inconsistent state until the save response has been returned from the server - for example, if a change to one property affects the computed value of other properties.

When a save creates a new record and a new primary key is returned from the server, any entities attached to the current ViewModel via a collection navigation property will have their foreign keys set to the new primary key. This behavior, combined with the usage of deep auto-saves, allows for complex object graphs to be constructed even before any model in the graph has been created.

When a save is in progress, the names of properties being saved are in contained in $savingProps.

Saving behavior can be further customized with $loadResponseFromSaves and $saveMode, listed below.

',6),M=e("p",null,[a("An "),e("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#api-callers"},"API Caller"),a(" for the "),e("code",null,"/delete"),a(" endpoint. Uses the instance's "),e("code",null,"$params"),a(" object for the "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Standard Parameters"),a(".")],-1),V=e("p",null,"If the object was loaded as a child of a collection, it will be removed from that collection upon being deleted. Note that ViewModels currently only support tracking of a single parent collection, so if an object is programmatically added to additional collections, it will only be removed from one of them upon delete.",-1),I=e("p",null,[a("Default "),e("code",null,"true"),a(" - controls if a ViewModel will be loaded with the data from the model returned by the "),e("code",null,"/save"),a(" endpoint when saved with the "),e("code",null,"$save"),a(" API caller. There is seldom any reason to disable this.")],-1),x=e("p",null,[a("When "),e("code",null,"$save.isLoading == true"),a(", contains the properties of the model currently being saved by "),e("code",null,"$save"),a(" (including auto-saves). Does not include non-dirty properties even if "),e("code",null,"$saveMode == 'whole'"),a(".")],-1),E=e("p",null,"This can be used to make per-property UI state changes during saves - for example, displaying progress indicators on/near individual inputs, or disabling input controls.",-1),q=s('

Configures which properties of the model are sent to the server during a save or bulk save.

"surgical" (default)

By default, only dirty properties (and always the primary key) are sent to the server when performing a save.

This improves the handling of concurrent changes being made by multiple users against different fields of the same entity at the same time - specifically, it prevents a user with a stale value of some field X from overwriting a more recent value of X in the database when the user is only making changes to some other property Y and has no intention of changing X.

Save mode "surgical" doesn't help when multiple users are editing field X at the same time - if such a scenario is applicable to your application, you must implement more advanced handling of concurrency conflicts.

WARNING

Surgical saves require DTOs on the server that are capable of determining which of their properties have been set by the model binder, as surgical saves are sent from the client by entirely omitting properties from the x-www-form-urlencoded body that is sent to the server.

The Generated C# DTOs implement the necessary logic for this; however, any Custom DTOs must have this logic manually written by you, the developer. Either implement the same pattern that can be seen in the Generated C# DTOs, or do not use surgical saves with Custom DTOs.

"whole"

All serializable properties of the object are sent back to the server with every save.

',2),R=e("p",null,"Returns true if the given property is flagged as dirty.",-1),j=e("p",null,"Manually set the dirty flag of the given property to the desired state. This seldom needs to be done explicitly, as mutating a property will automatically flag it as dirty.",-1),L=e("p",null,[a("If "),e("code",null,"dirty"),a(" is true and "),e("code",null,"triggerAutoSave"),a(" is false, auto-save (if enabled) will not be immediately triggered for this specific flag change. Note that a future change to any other property's dirty flag will still trigger a save of all dirty properties.")],-1),O=e("p",null,"Getter/setter that summarizes the model's property-level dirty flags. Returns true if any properties are dirty.",-1),N=e("p",null,"When set to false, all property dirty flags are cleared. When set to true, all properties are marked as dirty.",-1),B=e("h3",{id:"auto-save",tabindex:"-1"},[a("Auto-save "),e("a",{class:"header-anchor",href:"#auto-save","aria-label":'Permalink to "Auto-save"'},"​")],-1),F=s(`

Starts auto-saving of the instance when its savable data properties become dirty. Saves are performed with the $save API Caller (documented above) and will not be performed if the ViewModel has any validation errors - see Rules/Validation below.

ts
type AutoSaveOptions<TThis> = 
+{ 
+    /** Time, in milliseconds, to debounce saves for.  */
+    wait?: number;
+    
+    /** If true, auto-saving will also be enabled for all view models that are
+        reachable from the navigation properties & collections of the current view model. */
+    deep?: boolean;
+
+    /** Additional options to pass to the third parameter of lodash's \`debounce\` function. */
+    debounce?: DebounceSettings;
+
+    /** A function that will be called before autosaving that can return false to prevent a save. 
+        Only allowed if not using deep auto-saves.
+    */
+    predicate?: (viewModel: TThis) => boolean;
+}
`,2),G=e("p",null,[a("Turns off auto-saving of the instance. Does not recursively disable auto-saves on related instances if "),e("code",null,"deep"),a(" was used when auto-save was enabled.")],-1),U=e("p",null,"Returns true if auto-save is currently active on the instance.",-1),W=e("h3",{id:"bulk-saves",tabindex:"-1"},[a("Bulk saves "),e("a",{class:"header-anchor",href:"#bulk-saves","aria-label":'Permalink to "Bulk saves"'},"​")],-1),z=s(`

Bulk saves save all changes to an object graph in one API call and one database transaction. This includes creation, updates, and deletions of entities.

To use bulk saves, you can work with your ViewModel instances on the client much in the same way you would on the server with Entity Framework. Assign objects to reference navigation properties and modify scalar values to perform creates and updates. To perform deletions, you must call model.$remove() on the ViewModel you want to remove, similar how you would call DbSet<>.Remove(model) on the server.

If the client-side Rules/Validation report any errors for any of the models being saved in the operation, an error will be thrown.

On the server, each affected entity is handled through the same standard mechanisms as are used by individual saves or deletes (Behaviors, Data Sources, and Security Attributes), but with a bit of sugar on top:

  • All operations are wrapped in a single database transaction that is rolled back if any individual operation fails.
  • Foreign keys will be fixed up as new items are created, allowing a parent and child record to be created at the same time even when the client has no foreign key to link the two together.

For the response to a bulk save, the server will load and return the root ViewModel that $bulkSave was called upon, using the instance's $params object for the Standard Parameters.

ts
export interface BulkSaveOptions {
+  /** A predicate that will be applied to each modified model
+   * to determine if it should be included in the bulk save operation.
+   *
+   * The predicate is applied before validation (\`$hasError\`), allowing
+   * it to be used to skip over entities that have client validation errors
+   * that would otherwise cause the entire bulk save operation to fail.
+   * */
+  predicate?: (viewModel: ViewModel, action: "save" | "delete") => boolean;
+}
`,7),K=e("p",null,"Removes the item from its parent collection (if it is in a collection), and marks the item for deletion in the next bulk save.",-1),X=e("p",null,[a("Returns true if the instance was previously removed by calling "),e("code",null,"$remove()"),a(".")],-1),J=e("h3",{id:"rules-validation",tabindex:"-1"},[a("Rules/Validation "),e("a",{class:"header-anchor",href:"#rules-validation","aria-label":'Permalink to "Rules/Validation"'},"​")],-1),Y=e("p",null,[a("Add a custom validation rule to the ViewModel for the specified property. "),e("code",null,"identifier"),a(" should be a short, unique slug that describes the rule; it is not displayed in the UI, but is used if you wish to later remove the rule with "),e("code",null,"$removeRule()"),a(".")],-1),H=e("p",null,[a("The function you provide should take a single argument that contains the current value of the property, and should either return "),e("code",null,"true"),a(" to indicate that the validation rule has succeeded, or a string that will be displayed as an error message to the user.")],-1),Q=e("p",null,[a("Any failing validation rules on a ViewModel will prevent that ViewModel's "),e("code",null,"$save"),a(" caller from being invoked.")],-1),Z=e("p",null,"Remove a validation rule from the ViewModel for the specified property and rule identifier.",-1),ee=e("p",null,[a("This can be used to remove either a rule that was provided by the generated "),e("a",{href:"/Coalesce/stacks/vue/layers/metadata.html"},"Metadata Layer"),a(", or a custom rule that was added by "),e("code",null,"$addRule"),a(". Reference your generated metadata file "),e("code",null,"metadata.g.ts"),a(" to see any generated rules and the identifiers they use.")],-1),ae=e("p",null,[a("Returns an array of active rule functions for the specified property, or "),e("code",null,"undefined"),a(" if the property has no active validation rules.")],-1),te=e("p",null,[a("Returns a "),e("a",{href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator",target:"_blank",rel:"noreferrer"},"generator"),a(" that provides all error messages for either a specific property (if provided) or the entire model (if no prop argument is provided).")],-1),oe=e("div",{class:"tip custom-block"},[e("p",{class:"custom-block-title"},"TIP"),e("p",null,[a("You can obtain an array from a generator with "),e("code",null,"Array.from(vm.$getErrors())"),a(" or "),e("code",null,"[...vm.$getErrors()]")])],-1),se=s('

Indicates if any properties have validation errors.

Generated Members

API Callers

For each of the instance Methods of the type, an API Caller will be generated.

addTo*() Functions

For each collection navigation property, a method is generated that will create a new instance of the ViewModel for the collected type, add it to the collection, and then return the new object.

Many-to-many helper collections

For each collection navigation property annotated with [ManyToMany], a getter-only property is generated that returns a collection of the object on the far side of the many-to-many relationship. Nulls are filtered from this collection.

ListViewModels

The following members can be found on the generated ListViewModels, exported from viewmodels.g.ts as *TypeName*ListViewModel.

Data Properties

',11),ne=e("p",null,[a("Collection holding the results of the last successful invocation of the "),e("code",null,"$load"),a(),e("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#api-callers"},"API Caller"),a(".")],-1),le=e("h3",{id:"parameters-api-callers",tabindex:"-1"},[a("Parameters & API Callers "),e("a",{class:"header-anchor",href:"#parameters-api-callers","aria-label":'Permalink to "Parameters & API Callers"'},"​")],-1),re=e("p",null,[a("An object containing the "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Standard Parameters"),a(" to be used for the "),e("code",null,"$load"),a(" and "),e("code",null,"$count"),a(" API callers.")],-1),ie=e("p",null,[a("Getter/setter wrapper around "),e("code",null,"$params.dataSource"),a(". Takes an instance of a "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html"},"Data Source"),a(" class "),e("a",{href:"/Coalesce/stacks/vue/layers/models.html"},"generated in the Model Layer"),a(".")],-1),de=e("p",null,[a("Getter/setter wrapper around "),e("code",null,"$params.includes"),a(". See "),e("a",{href:"/Coalesce/concepts/includes.html"},"Includes String"),a(" for more information.")],-1),ce=s('

An API Caller for the /list endpoint. Uses the instance's $params object for the Standard Parameters.

Results are available in the $items property. The result property of the $load API Caller contains the raw results and is not recommended for use in general development - $items should always be preferred.

',2),pe=s('

An API Caller for the /count endpoint. Uses the instance's $params object for the Standard Parameters.

The result is available in $count.result - this API Caller does not interact with other properties on the ListViewModel like $pageSize or $pageCount.

',2),he=e("p",null,[a("Properties which indicate if "),e("code",null,"$page"),a(" can be decremented or incremented, respectively. "),e("code",null,"$pageCount"),a(" and "),e("code",null,"$page"),a(" are used to make this determination.")],-1),ue=e("p",null,[a("Methods that will decrement or increment "),e("code",null,"$page"),a(", respectively. Each does nothing if there is no previous or next page as returned by "),e("code",null,"$hasPreviousPage"),a(" and "),e("code",null,"$hasNextPage"),a(".")],-1),me=e("p",null,[a("Getter/setter wrapper for "),e("code",null,"$params.page"),a(". Controls the page that will be requested on the next invocation of "),e("code",null,"$load"),a(".")],-1),fe=e("p",null,[a("Getter/setter wrapper for "),e("code",null,"$params.pageSize"),a(". Controls the page that will be requested on the next invocation of "),e("code",null,"$load"),a(".")],-1),ye=e("p",null,[a("Shorthand for "),e("code",null,"$load.pageCount"),a(" - returns the page count reported by the last successful invocation of "),e("code",null,"$load"),a(".")],-1),ge=e("h3",{id:"auto-load",tabindex:"-1"},[a("Auto-Load "),e("a",{class:"header-anchor",href:"#auto-load","aria-label":'Permalink to "Auto-Load"'},"​")],-1),ve=s(`

Starts auto-loading of the list as changes to its parameters occur. Loads are performed with the $load API Caller.

ts
type AutoLoadOptions<TThis> =
+{ 
+    /** Time, in milliseconds, to debounce loads for.  */
+    wait?: number;
+
+    /** Additional options to pass to the third parameter of lodash's \`debounce\` function. */
+    debounce?: DebounceSettings;
+
+    /** A function that will be called before loading that can return false to prevent a load. */
+    predicate?: (viewModel: TThis) => boolean;
+}
`,2),be=e("p",null,"Manually turns off auto-loading of the instance.",-1),_e=e("h3",{id:"auto-save-1",tabindex:"-1"},[a("Auto-save "),e("a",{class:"header-anchor",href:"#auto-save-1","aria-label":'Permalink to "Auto-save"'},"​")],-1),we=e("p",null,[a("Enables auto-save for the items in the list, propagating to new items as they're added or loaded. See "),e("a",{href:"#member-item-autosave"},"ViewModel auto-save documentation"),a(" for more details.")],-1),De=e("p",null,[a("Turns off auto-saving of the items in the list, and turns of propagation of auto-save to any future items if auto-save was previously turned on for the list. Only affects items that are currently in the list's "),e("code",null,"$items"),a(".")],-1),Ce=s('

Returns true if auto-save is currently active on the instance.

Generated Members

API Callers

For each of the static Methods on the type, an API Caller will be created.

Service ViewModels

The following members can be found on the generated Service ViewModels, exported from viewmodels.g.ts as <ServiceName>ViewModel.

Generated Members

API Callers

For each method of the Service, an API Caller will be created.

',9);function Pe(Ae,Te,ke,$e,Se,Me){const t=l("Prop");return r(),i("div",null,[c,o(t,{def:"readonly $metadata: ModelType",lang:"ts"}),p,o(t,{def:"readonly $stableId: number",lang:"ts"}),h,u,o(t,{def:"$primaryKey: string | number",lang:"ts"}),m,o(t,{def:"$display(prop?: string | Property): string",lang:"ts"}),f,o(t,{def:"$addChild(prop: string | ModelCollectionNavigationProperty, initialDirtyData?: {})",lang:"ts"}),y,g,o(t,{def:`$load: ItemApiState; +$load(id?: TKey) => ItemResultPromise;`,lang:"ts",idPrefix:"member-item"}),v,o(t,{def:"$params: DataSourceParameters",lang:"ts",idPrefix:"member-item"}),b,o(t,{def:"$dataSource: DataSource",lang:"ts",idPrefix:"member-item"}),_,o(t,{def:"$includes: string | null",lang:"ts",idPrefix:"member-item"}),w,o(t,{def:"$loadCleanData(source: {} | TModel, purgeUnsaved = false)",lang:"ts"}),D,C,P,A,o(t,{def:"$loadDirtyData(source: {} | TModel)",lang:"ts"}),T,o(t,{def:"constructor(initialDirtyData?: {} | TModel | null)",lang:"ts"}),k,$,o(t,{def:`$save: ItemApiState; +$save(overrideProps?: Partial) => ItemResultPromise;`,lang:"ts",idPrefix:"member-item"}),S,o(t,{def:`$delete: ItemApiState; +$delete() => ItemResultPromise;`,lang:"ts",idPrefix:"member-item"}),M,V,o(t,{def:"$loadResponseFromSaves: boolean",lang:"ts"}),I,o(t,{def:"$savingProps: ReadonlySet",lang:"ts"}),x,E,o(t,{def:"$saveMode: 'surgical' | 'whole'",lang:"ts"}),q,o(t,{def:"$getPropDirty(propName: string): boolean",lang:"ts"}),R,o(t,{def:"$setPropDirty(propName: string, dirty: boolean = true, triggerAutoSave = true)",lang:"ts"}),j,L,o(t,{def:"$isDirty: boolean",lang:"ts"}),O,N,B,o(t,{def:`// Vue Options API +$startAutoSave(vue: Vue, options: AutoSaveOptions = {}) +  +// Vue Composition API +$useAutoSave(options: AutoSaveOptions = {})`,lang:"ts",id:"member-item-autosave"}),F,o(t,{def:"$stopAutoSave(): void",lang:"ts"}),G,o(t,{def:"readonly $isAutoSaveEnabled: boolean",lang:"ts"}),U,W,o(t,{def:`$bulkSave: ItemApiState; +$bulkSave(options: BulkSaveOptions) => ItemResultPromise;`,lang:"ts"}),z,o(t,{def:"$remove(): void",lang:"ts"}),K,o(t,{def:"readonly $isRemoved: boolean",lang:"ts"}),X,J,o(t,{def:"$addRule(prop: string | Property, identifier: string, rule: (val: any) => true | string)",lang:"ts"}),Y,H,Q,o(t,{def:"$removeRule(prop: string | Property, identifier: string)",lang:"ts"}),Z,ee,o(t,{def:"$getRules(prop: string | Property): ((val: any) => string | true)[]",lang:"ts"}),ae,o(t,{def:"$getErrors(prop?: string | Property): Generator",lang:"ts"}),te,oe,o(t,{def:"readonly $hasError: boolean",lang:"ts"}),se,o(t,{def:"readonly $items: T[]",lang:"ts"}),ne,le,o(t,{def:"$params: ListParameters",lang:"ts",idPrefix:"member-list"}),re,o(t,{def:"$dataSource: DataSource",lang:"ts",idPrefix:"member-list"}),ie,o(t,{def:"$includes: string | null",lang:"ts",idPrefix:"member-list"}),de,o(t,{def:`$load: ListApiState; +$load() => ListResultPromise`,lang:"ts",idPrefix:"member-list"}),ce,o(t,{def:`$count: ItemApiState; +$count() => ItemResultPromise`,lang:"ts"}),pe,o(t,{def:`readonly $hasPreviousPage: boolean +readonly $hasNextPage: boolean`,lang:"ts"}),he,o(t,{def:`$previousPage(): void +$nextPage(): void`,lang:"ts"}),ue,o(t,{def:"$page: number",lang:"ts"}),me,o(t,{def:"$pageSize: number",lang:"ts"}),fe,o(t,{def:"readonly $pageCount: number",lang:"ts"}),ye,ge,o(t,{def:`// Vue Options API +$startAutoLoad(vue: Vue, options: AutoLoadOptions = {}) +  +// Vue Composition API +$useAutoLoad(options: AutoLoadOptions = {})`,lang:"ts",id:"member-list-autoLoad"}),ve,o(t,{def:"$stopAutoLoad()",lang:"ts"}),be,_e,o(t,{def:`// Vue Options API +$startAutoSave(vue: Vue, options: AutoSaveOptions = {}) +  +// Vue Composition API +$useAutoSave(options: AutoSaveOptions = {})`,lang:"ts",id:"member-list-autosave"}),we,o(t,{def:"$stopAutoSave(): void",lang:"ts"}),De,o(t,{def:"readonly $isAutoSaveEnabled: boolean",lang:"ts"}),Ce])}const xe=n(d,[["render",Pe]]);export{Ie as __pageData,xe as default}; diff --git a/assets/stacks_vue_layers_viewmodels.md.y7kFb0eu.lean.js b/assets/stacks_vue_layers_viewmodels.md.y7kFb0eu.lean.js new file mode 100644 index 000000000..5ab8a1b6c --- /dev/null +++ b/assets/stacks_vue_layers_viewmodels.md.y7kFb0eu.lean.js @@ -0,0 +1,21 @@ +import{_ as n,D as l,o as r,c as i,I as o,R as s,k as e,a}from"./chunks/framework.g9eZ-ZSs.js";const Ie=JSON.parse('{"title":"Vue ViewModel Layer","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/layers/viewmodels.md","filePath":"stacks/vue/layers/viewmodels.md"}'),d={name:"stacks/vue/layers/viewmodels.md"},c=s("",16),p=e("p",null,[a("The metadata object from the "),e("a",{href:"/Coalesce/stacks/vue/layers/metadata.html"},"Metadata Layer"),a(" layer for the type represented by the ViewModel.")],-1),h=e("p",null,"An immutable number that is unique among all ViewModel instances, regardless of type.",-1),u=e("p",null,[a("Useful for uniquely identifying instances with "),e("code",null,':key="vm.$stableId"'),a(" in a Vue component, especially for instances that lack a primary key.")],-1),m=e("p",null,"A getter/setter property that wraps the primary key of the model. Used to interact with the primary key of any ViewModel in a polymorphic way.",-1),f=e("p",null,"Returns a string representation of the object, or one of its properties if specified, suitable for display.",-1),y=e("p",null,[a("Creates a new instance of an item for the specified child model collection, adds it to that collection, and returns the item. If "),e("code",null,"initialDirtyData"),a(" is provided, it will be loaded into the new instance with "),e("code",null,"$loadDirtyData()"),a(".")],-1),g=e("h3",{id:"loading-parameters",tabindex:"-1"},[a("Loading & Parameters "),e("a",{class:"header-anchor",href:"#loading-parameters","aria-label":'Permalink to "Loading & Parameters"'},"​")],-1),v=s("",1),b=e("p",null,[a("An object containing the "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Standard Parameters"),a(" to be used for the "),e("code",null,"$load"),a(", "),e("code",null,"$save"),a(", "),e("code",null,"$bulkSave"),a(", and "),e("code",null,"$delete"),a(" API callers.")],-1),_=e("p",null,[a("Getter/setter wrapper around "),e("code",null,"$params.dataSource"),a(". Takes an instance of a "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html"},"Data Source"),a(" class "),e("a",{href:"/Coalesce/stacks/vue/layers/models.html"},"generated in the Model Layer"),a(".")],-1),w=e("p",null,[a("Getter/setter wrapper around "),e("code",null,"$params.includes"),a(". See "),e("a",{href:"/Coalesce/concepts/includes.html"},"Includes String"),a(" for more information.")],-1),D=e("p",null,"Loads data from the provided model into the current ViewModel, and then clears all dirty flags.",-1),C=e("p",null,"Data is loaded recursively into all related ViewModel instances, preserving existing instances whose primary keys match the incoming data.",-1),P=e("p",null,[a("If auto-save is enabled, only non-dirty properties are updated. This prevents user input that is pending a save from being overwritten by the response from an auto-save "),e("code",null,"/save"),a(" request.")],-1),A=e("p",null,[a("If "),e("code",null,"purgeUnsaved"),a(" is true, items without a primary key will be dropped from collection navigation properties. This is used by the "),e("code",null,"$load"),a(" caller in order to fully reset the object graph with the state from the server.")],-1),T=e("p",null,[a("Same as "),e("code",null,"$loadCleanData"),a(", but does not clear any existing dirty flags, nor does it clear any dirty flags that will be set while mutating the data properties of any ViewModel instance that gets loaded.")],-1),k=e("p",null,[a("Create a new instance of the ViewModel, loading the given initial data with "),e("code",null,"$loadDirtyData()"),a(" if provided.")],-1),$=e("h3",{id:"saving-and-deleting",tabindex:"-1"},[a("Saving and Deleting "),e("a",{class:"header-anchor",href:"#saving-and-deleting","aria-label":'Permalink to "Saving and Deleting"'},"​")],-1),S=s("",6),M=e("p",null,[a("An "),e("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#api-callers"},"API Caller"),a(" for the "),e("code",null,"/delete"),a(" endpoint. Uses the instance's "),e("code",null,"$params"),a(" object for the "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Standard Parameters"),a(".")],-1),V=e("p",null,"If the object was loaded as a child of a collection, it will be removed from that collection upon being deleted. Note that ViewModels currently only support tracking of a single parent collection, so if an object is programmatically added to additional collections, it will only be removed from one of them upon delete.",-1),I=e("p",null,[a("Default "),e("code",null,"true"),a(" - controls if a ViewModel will be loaded with the data from the model returned by the "),e("code",null,"/save"),a(" endpoint when saved with the "),e("code",null,"$save"),a(" API caller. There is seldom any reason to disable this.")],-1),x=e("p",null,[a("When "),e("code",null,"$save.isLoading == true"),a(", contains the properties of the model currently being saved by "),e("code",null,"$save"),a(" (including auto-saves). Does not include non-dirty properties even if "),e("code",null,"$saveMode == 'whole'"),a(".")],-1),E=e("p",null,"This can be used to make per-property UI state changes during saves - for example, displaying progress indicators on/near individual inputs, or disabling input controls.",-1),q=s("",2),R=e("p",null,"Returns true if the given property is flagged as dirty.",-1),j=e("p",null,"Manually set the dirty flag of the given property to the desired state. This seldom needs to be done explicitly, as mutating a property will automatically flag it as dirty.",-1),L=e("p",null,[a("If "),e("code",null,"dirty"),a(" is true and "),e("code",null,"triggerAutoSave"),a(" is false, auto-save (if enabled) will not be immediately triggered for this specific flag change. Note that a future change to any other property's dirty flag will still trigger a save of all dirty properties.")],-1),O=e("p",null,"Getter/setter that summarizes the model's property-level dirty flags. Returns true if any properties are dirty.",-1),N=e("p",null,"When set to false, all property dirty flags are cleared. When set to true, all properties are marked as dirty.",-1),B=e("h3",{id:"auto-save",tabindex:"-1"},[a("Auto-save "),e("a",{class:"header-anchor",href:"#auto-save","aria-label":'Permalink to "Auto-save"'},"​")],-1),F=s("",2),G=e("p",null,[a("Turns off auto-saving of the instance. Does not recursively disable auto-saves on related instances if "),e("code",null,"deep"),a(" was used when auto-save was enabled.")],-1),U=e("p",null,"Returns true if auto-save is currently active on the instance.",-1),W=e("h3",{id:"bulk-saves",tabindex:"-1"},[a("Bulk saves "),e("a",{class:"header-anchor",href:"#bulk-saves","aria-label":'Permalink to "Bulk saves"'},"​")],-1),z=s("",7),K=e("p",null,"Removes the item from its parent collection (if it is in a collection), and marks the item for deletion in the next bulk save.",-1),X=e("p",null,[a("Returns true if the instance was previously removed by calling "),e("code",null,"$remove()"),a(".")],-1),J=e("h3",{id:"rules-validation",tabindex:"-1"},[a("Rules/Validation "),e("a",{class:"header-anchor",href:"#rules-validation","aria-label":'Permalink to "Rules/Validation"'},"​")],-1),Y=e("p",null,[a("Add a custom validation rule to the ViewModel for the specified property. "),e("code",null,"identifier"),a(" should be a short, unique slug that describes the rule; it is not displayed in the UI, but is used if you wish to later remove the rule with "),e("code",null,"$removeRule()"),a(".")],-1),H=e("p",null,[a("The function you provide should take a single argument that contains the current value of the property, and should either return "),e("code",null,"true"),a(" to indicate that the validation rule has succeeded, or a string that will be displayed as an error message to the user.")],-1),Q=e("p",null,[a("Any failing validation rules on a ViewModel will prevent that ViewModel's "),e("code",null,"$save"),a(" caller from being invoked.")],-1),Z=e("p",null,"Remove a validation rule from the ViewModel for the specified property and rule identifier.",-1),ee=e("p",null,[a("This can be used to remove either a rule that was provided by the generated "),e("a",{href:"/Coalesce/stacks/vue/layers/metadata.html"},"Metadata Layer"),a(", or a custom rule that was added by "),e("code",null,"$addRule"),a(". Reference your generated metadata file "),e("code",null,"metadata.g.ts"),a(" to see any generated rules and the identifiers they use.")],-1),ae=e("p",null,[a("Returns an array of active rule functions for the specified property, or "),e("code",null,"undefined"),a(" if the property has no active validation rules.")],-1),te=e("p",null,[a("Returns a "),e("a",{href:"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator",target:"_blank",rel:"noreferrer"},"generator"),a(" that provides all error messages for either a specific property (if provided) or the entire model (if no prop argument is provided).")],-1),oe=e("div",{class:"tip custom-block"},[e("p",{class:"custom-block-title"},"TIP"),e("p",null,[a("You can obtain an array from a generator with "),e("code",null,"Array.from(vm.$getErrors())"),a(" or "),e("code",null,"[...vm.$getErrors()]")])],-1),se=s("",11),ne=e("p",null,[a("Collection holding the results of the last successful invocation of the "),e("code",null,"$load"),a(),e("a",{href:"/Coalesce/stacks/vue/layers/api-clients.html#api-callers"},"API Caller"),a(".")],-1),le=e("h3",{id:"parameters-api-callers",tabindex:"-1"},[a("Parameters & API Callers "),e("a",{class:"header-anchor",href:"#parameters-api-callers","aria-label":'Permalink to "Parameters & API Callers"'},"​")],-1),re=e("p",null,[a("An object containing the "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-parameters"},"Standard Parameters"),a(" to be used for the "),e("code",null,"$load"),a(" and "),e("code",null,"$count"),a(" API callers.")],-1),ie=e("p",null,[a("Getter/setter wrapper around "),e("code",null,"$params.dataSource"),a(". Takes an instance of a "),e("a",{href:"/Coalesce/modeling/model-components/data-sources.html"},"Data Source"),a(" class "),e("a",{href:"/Coalesce/stacks/vue/layers/models.html"},"generated in the Model Layer"),a(".")],-1),de=e("p",null,[a("Getter/setter wrapper around "),e("code",null,"$params.includes"),a(". See "),e("a",{href:"/Coalesce/concepts/includes.html"},"Includes String"),a(" for more information.")],-1),ce=s("",2),pe=s("",2),he=e("p",null,[a("Properties which indicate if "),e("code",null,"$page"),a(" can be decremented or incremented, respectively. "),e("code",null,"$pageCount"),a(" and "),e("code",null,"$page"),a(" are used to make this determination.")],-1),ue=e("p",null,[a("Methods that will decrement or increment "),e("code",null,"$page"),a(", respectively. Each does nothing if there is no previous or next page as returned by "),e("code",null,"$hasPreviousPage"),a(" and "),e("code",null,"$hasNextPage"),a(".")],-1),me=e("p",null,[a("Getter/setter wrapper for "),e("code",null,"$params.page"),a(". Controls the page that will be requested on the next invocation of "),e("code",null,"$load"),a(".")],-1),fe=e("p",null,[a("Getter/setter wrapper for "),e("code",null,"$params.pageSize"),a(". Controls the page that will be requested on the next invocation of "),e("code",null,"$load"),a(".")],-1),ye=e("p",null,[a("Shorthand for "),e("code",null,"$load.pageCount"),a(" - returns the page count reported by the last successful invocation of "),e("code",null,"$load"),a(".")],-1),ge=e("h3",{id:"auto-load",tabindex:"-1"},[a("Auto-Load "),e("a",{class:"header-anchor",href:"#auto-load","aria-label":'Permalink to "Auto-Load"'},"​")],-1),ve=s("",2),be=e("p",null,"Manually turns off auto-loading of the instance.",-1),_e=e("h3",{id:"auto-save-1",tabindex:"-1"},[a("Auto-save "),e("a",{class:"header-anchor",href:"#auto-save-1","aria-label":'Permalink to "Auto-save"'},"​")],-1),we=e("p",null,[a("Enables auto-save for the items in the list, propagating to new items as they're added or loaded. See "),e("a",{href:"#member-item-autosave"},"ViewModel auto-save documentation"),a(" for more details.")],-1),De=e("p",null,[a("Turns off auto-saving of the items in the list, and turns of propagation of auto-save to any future items if auto-save was previously turned on for the list. Only affects items that are currently in the list's "),e("code",null,"$items"),a(".")],-1),Ce=s("",9);function Pe(Ae,Te,ke,$e,Se,Me){const t=l("Prop");return r(),i("div",null,[c,o(t,{def:"readonly $metadata: ModelType",lang:"ts"}),p,o(t,{def:"readonly $stableId: number",lang:"ts"}),h,u,o(t,{def:"$primaryKey: string | number",lang:"ts"}),m,o(t,{def:"$display(prop?: string | Property): string",lang:"ts"}),f,o(t,{def:"$addChild(prop: string | ModelCollectionNavigationProperty, initialDirtyData?: {})",lang:"ts"}),y,g,o(t,{def:`$load: ItemApiState; +$load(id?: TKey) => ItemResultPromise;`,lang:"ts",idPrefix:"member-item"}),v,o(t,{def:"$params: DataSourceParameters",lang:"ts",idPrefix:"member-item"}),b,o(t,{def:"$dataSource: DataSource",lang:"ts",idPrefix:"member-item"}),_,o(t,{def:"$includes: string | null",lang:"ts",idPrefix:"member-item"}),w,o(t,{def:"$loadCleanData(source: {} | TModel, purgeUnsaved = false)",lang:"ts"}),D,C,P,A,o(t,{def:"$loadDirtyData(source: {} | TModel)",lang:"ts"}),T,o(t,{def:"constructor(initialDirtyData?: {} | TModel | null)",lang:"ts"}),k,$,o(t,{def:`$save: ItemApiState; +$save(overrideProps?: Partial) => ItemResultPromise;`,lang:"ts",idPrefix:"member-item"}),S,o(t,{def:`$delete: ItemApiState; +$delete() => ItemResultPromise;`,lang:"ts",idPrefix:"member-item"}),M,V,o(t,{def:"$loadResponseFromSaves: boolean",lang:"ts"}),I,o(t,{def:"$savingProps: ReadonlySet",lang:"ts"}),x,E,o(t,{def:"$saveMode: 'surgical' | 'whole'",lang:"ts"}),q,o(t,{def:"$getPropDirty(propName: string): boolean",lang:"ts"}),R,o(t,{def:"$setPropDirty(propName: string, dirty: boolean = true, triggerAutoSave = true)",lang:"ts"}),j,L,o(t,{def:"$isDirty: boolean",lang:"ts"}),O,N,B,o(t,{def:`// Vue Options API +$startAutoSave(vue: Vue, options: AutoSaveOptions = {}) +  +// Vue Composition API +$useAutoSave(options: AutoSaveOptions = {})`,lang:"ts",id:"member-item-autosave"}),F,o(t,{def:"$stopAutoSave(): void",lang:"ts"}),G,o(t,{def:"readonly $isAutoSaveEnabled: boolean",lang:"ts"}),U,W,o(t,{def:`$bulkSave: ItemApiState; +$bulkSave(options: BulkSaveOptions) => ItemResultPromise;`,lang:"ts"}),z,o(t,{def:"$remove(): void",lang:"ts"}),K,o(t,{def:"readonly $isRemoved: boolean",lang:"ts"}),X,J,o(t,{def:"$addRule(prop: string | Property, identifier: string, rule: (val: any) => true | string)",lang:"ts"}),Y,H,Q,o(t,{def:"$removeRule(prop: string | Property, identifier: string)",lang:"ts"}),Z,ee,o(t,{def:"$getRules(prop: string | Property): ((val: any) => string | true)[]",lang:"ts"}),ae,o(t,{def:"$getErrors(prop?: string | Property): Generator",lang:"ts"}),te,oe,o(t,{def:"readonly $hasError: boolean",lang:"ts"}),se,o(t,{def:"readonly $items: T[]",lang:"ts"}),ne,le,o(t,{def:"$params: ListParameters",lang:"ts",idPrefix:"member-list"}),re,o(t,{def:"$dataSource: DataSource",lang:"ts",idPrefix:"member-list"}),ie,o(t,{def:"$includes: string | null",lang:"ts",idPrefix:"member-list"}),de,o(t,{def:`$load: ListApiState; +$load() => ListResultPromise`,lang:"ts",idPrefix:"member-list"}),ce,o(t,{def:`$count: ItemApiState; +$count() => ItemResultPromise`,lang:"ts"}),pe,o(t,{def:`readonly $hasPreviousPage: boolean +readonly $hasNextPage: boolean`,lang:"ts"}),he,o(t,{def:`$previousPage(): void +$nextPage(): void`,lang:"ts"}),ue,o(t,{def:"$page: number",lang:"ts"}),me,o(t,{def:"$pageSize: number",lang:"ts"}),fe,o(t,{def:"readonly $pageCount: number",lang:"ts"}),ye,ge,o(t,{def:`// Vue Options API +$startAutoLoad(vue: Vue, options: AutoLoadOptions = {}) +  +// Vue Composition API +$useAutoLoad(options: AutoLoadOptions = {})`,lang:"ts",id:"member-list-autoLoad"}),ve,o(t,{def:"$stopAutoLoad()",lang:"ts"}),be,_e,o(t,{def:`// Vue Options API +$startAutoSave(vue: Vue, options: AutoSaveOptions = {}) +  +// Vue Composition API +$useAutoSave(options: AutoSaveOptions = {})`,lang:"ts",id:"member-list-autosave"}),we,o(t,{def:"$stopAutoSave(): void",lang:"ts"}),De,o(t,{def:"readonly $isAutoSaveEnabled: boolean",lang:"ts"}),Ce])}const xe=n(d,[["render",Pe]]);export{Ie as __pageData,xe as default}; diff --git a/assets/stacks_vue_overview.md.PKO1LKIV.js b/assets/stacks_vue_overview.md.PKO1LKIV.js new file mode 100644 index 000000000..0142c0cdd --- /dev/null +++ b/assets/stacks_vue_overview.md.PKO1LKIV.js @@ -0,0 +1 @@ +import{_ as e,o as a,c as t,R as o}from"./chunks/framework.g9eZ-ZSs.js";const f=JSON.parse('{"title":"Vue Overview","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/overview.md","filePath":"stacks/vue/overview.md"}'),s={name:"stacks/vue/overview.md"},r=o('

Vue Overview

The Vue stack for Coalesce has been designed from the ground up to be used to build modern web applications using current technologies like Vite, ES Modules, and more. It enables you to use all of the features of Vue.js, including building a SPA, and the ability to use modern component frameworks like Vuetify.

Getting Started

Check out Getting Started with Vue to learn how to get a new Coalesce Vue project up and running.

TypeScript Layers

The generated code for the Vue stack all builds on the coalesce-vue NPM package which contains most of the core functionality of the Vue stack. Its version should generally be kept in sync with the IntelliTect.Coalesce NuGet packages in your project.

Both the generated code and coalesce-vue are split into four layers, with each layer building on the layers underneath. From the bottom, these layers are:

Metadata Layer

The metadata layer, generated as metadata.g.ts, contains information about the types, properties, methods, and other components of your data model. Because Vue applications are typically compiled into a set of static assets, it is necessary for the frontend code to have a representation of your data model as an analog to the ReflectionRepository that is available at runtime in your .NET app.

Read more about the Metadata layer

Model Layer

The model layer, generated as models.g.ts, contains a set of TypeScript interfaces that represent each client-exposed type in your data model. Each interface contains all the Properties of that type, as well as a $metadata property that references the metadata object for that type. Enums and Data Sources are also represented in the model layer.

Read more about the Model layer

API Client Layer

The API client layer, generated as api-clients.g.ts, exports a class for each API controller that was generated for your data model. These classes are stateless and provide one method for each API endpoint. This includes both the standard set of endpoints created for Entity Models and Custom DTOs, as well as any custom Methods on the aforementioned types, as well as any methods on your Services.

Read more about the API Client layer

ViewModel Layer

The ViewModel layer, generated as viewmodels.g.ts, exports a ViewModel class for each API-backed type in your data model (Entity Models, Custom DTOs, and Services). It also exports a ListViewModel type for Entity Models and Custom DTOs.

These classes provide a wide array of functionality that is useful when interacting with your data model through a user interface. The generated ViewModels are the primary way that Coalesce is used when developing a Vue application.

Read more about the ViewModel layer

Vue Components

The Vue stack for Coalesce provides a set of components based on Vuetify, packaged up in an NPM package coalesce-vue-vuetify2 or coalesce-vue-vuetify3. These components are driven primarily by the Metadata Layer, and include both low level input and display components like c-input and c-display that are highly reusable in the custom pages you'll build in your application, as well as high-level components like c-admin-table-page and c-admin-editor-page that constitute entire pages.

Read more about the Vuetify Components here.

Admin Views

The Vue.js stack for Coalesce provides some high level components that provide functionality of whole pages like c-admin-table-page and c-admin-editor-page.

The template described in Getting Started with Vue comes with routes already in place for these page-level components. For example, /admin/Person for a table, /admin/Person/edit to create a new Person, and /admin/Person/edit/:id to edit a Person.

',28),l=[r];function c(n,i,d,h,p,m){return a(),t("div",null,l)}const v=e(s,[["render",c]]);export{f as __pageData,v as default}; diff --git a/assets/stacks_vue_overview.md.PKO1LKIV.lean.js b/assets/stacks_vue_overview.md.PKO1LKIV.lean.js new file mode 100644 index 000000000..e33bf9849 --- /dev/null +++ b/assets/stacks_vue_overview.md.PKO1LKIV.lean.js @@ -0,0 +1 @@ +import{_ as e,o as a,c as t,R as o}from"./chunks/framework.g9eZ-ZSs.js";const f=JSON.parse('{"title":"Vue Overview","description":"","frontmatter":{},"headers":[],"relativePath":"stacks/vue/overview.md","filePath":"stacks/vue/overview.md"}'),s={name:"stacks/vue/overview.md"},r=o("",28),l=[r];function c(n,i,d,h,p,m){return a(),t("div",null,l)}const v=e(s,[["render",c]]);export{f as __pageData,v as default}; diff --git a/assets/stacks_vue_vue2-to-vue3.md.hLyUCPCI.js b/assets/stacks_vue_vue2-to-vue3.md.hLyUCPCI.js new file mode 100644 index 000000000..3172d456f --- /dev/null +++ b/assets/stacks_vue_vue2-to-vue3.md.hLyUCPCI.js @@ -0,0 +1,177 @@ +import{_ as s,o as n,c as a,R as l}from"./chunks/framework.g9eZ-ZSs.js";const d=JSON.parse('{"title":"Vue 2 to Vue 3","description":"","frontmatter":{"pageClass":"wide-page"},"headers":[],"relativePath":"stacks/vue/vue2-to-vue3.md","filePath":"stacks/vue/vue2-to-vue3.md"}'),p={name:"stacks/vue/vue2-to-vue3.md"},o=l(`

Vue 2 to Vue 3

If you're already experienced with Vue 2 but are new to Vue 3, or if you're migrating an existing Vue 2 app to Vue 3, you should first read through the official migration guide.

Vuetify also offers a migration guide to upgrade from Vuetify 2 to Vuetify 3.

If you're new to Vue entirely, check out the rest of Vue docs and pick your learning path.

Coalesce Upgrade Steps

The changes specific to Coalesce when migrating from Vue2 to Vue3 are pretty minimal. Most of your work will be in following the Vue 3 Migration Guide and the Vuetify 3 Migration Guide.

The table below contains the Coalesce-specific changes when migrating to Vue 3. However, the easiest migration path may be to disregard the table below and instead, instantiate the Coalesce Vue template or look at it on GitHub and compare individual files between your project and the template side by side and ingest the changes that you observe.

LocationOld (Vue 2)New (Vue 3)

package.json

json
{
+  "dependencies": {
+    "coalesce-vue-vuetify2": "x"
+  }
+}
json
{
+  "dependencies": {
+    "coalesce-vue-vuetify3": "x"
+  }
+}

vite.config.ts

ts
import { CoalesceVuetifyResolver } from "coalesce-vue-vuetify2/lib/build"
ts
import { CoalesceVuetifyResolver } from "coalesce-vue-vuetify3/build"
+
+// Custom SASS options and \`optimizeDeps\` configuration can be removed
+// since Vuetify3 no longer uses deprecated sass features,
+// and pre-bundling styles no longer has appreciable benefit.

main.ts

ts
import "coalesce-vue-vuetify2/dist/coalesce-vue-vuetify.css"
+
+// Either of these:
+import CoalesceVuetify from 'coalesce-vue-vuetify2/lib'
+import CoalesceVuetify from 'coalesce-vue-vuetify2'
+
+Vue.use(CoalesceVuetify, {
+  metadata: $metadata,
+});
ts
import "coalesce-vue-vuetify3/styles.css"
+
+
+import { createCoalesceVuetify } from "coalesce-vue-vuetify3";
+
+
+const coalesceVuetify = createCoalesceVuetify({
+  metadata: $metadata,
+});
+app.use(coalesceVuetify);

router.ts

ts
// Either of these:
+import { CAdminTablePage, CAdminEditorPage } from 'coalesce-vue-vuetify2/lib';
+import { CAdminTablePage, CAdminEditorPage } from 'coalesce-vue-vuetify2';
ts

+import { CAdminEditorPage, CAdminTablePage } from "coalesce-vue-vuetify3";

Vitest/Jest tests

If you had a global test setup file performing Vue configuration, you can likely remove it entirely, or at least remove the parts that configure Vue. Vue3 does not operate on global configuration like Vue2 did.

See test-utils.ts and HelloWorld.spec.ts in the template for examples of Vue3 component testing.

From Class Components to <script setup>

The components in the Coalesce template for Vue 3 have switched from vue-class-component to Vue Composition API with <script setup>, the official recommendation for building full Vue 3 applications.

If you're used to writing components in Vue 2 with vue-class-component and vue-property-decorator, you can use this table of comparisons as a quick reference of what the equivalent features are using <script setup> and Vue Composition API. That said, this is not a replacement for learning and understanding the composition API. You should read the Composition API FAQ as well as the Reactivity Fundamentals documentation (make sure to set the API preference in the top left to Composition!).

If you'd like to continue using class components with Vue 3 (e.g. upgrading an existing project where rewriting all components is not feasible), you can try switching to vue-facing-decorator.

Note

The examples below assume that unplugin-auto-import is being used (included in the Coalesce Vue3 template), eliminating the need to manually import common Vue Composition API functions.

FeatureClass ComponentScript Setup

Coalesce ViewModel and ListViewModel usage

vue
<script lang="ts">
+import { Vue, Component } from "vue-property-decorator";
+import { PersonViewModel, PersonListViewModel } from "@/viewmodels.g";
+
+@Component({})
+export default class MyComponent extends Vue {
+  public person = new PersonViewModel();
+  public list = new PersonListViewModel();
+
+  async created() {
+    await person.$load();
+    await list.$load();
+
+    person.$startAutoSave(this);
+    list.$startAutoLoad(this);
+  }
+}
+</script>
vue
<script lang="ts" setup>
+import { PersonViewModel, PersonListViewModel } from "@/viewmodels.g";
+
+const person = new PersonViewModel();
+const list = new PersonListViewModel();
+
+person.$useAutoSave();
+list.$useAutoLoad();
+
+// If you need to await an async operation during component creation, 
+// use an IIFE so that the component mount is not delayed.
+(async function created() {
+  await person.$load();
+  await list.$load();
+})();
+</script>

@Prop, @Watch

vue
<script lang="ts">
+import { Vue, Component, Prop, Watch } from "vue-property-decorator";
+
+@Component({})
+export default class MyComponent extends Vue {
+  @Prop({ default: "Student" })
+  label!: string;
+
+  @Prop({ required: true })
+  student!: ApplicationUserViewModel;
+
+  @Watch("label")
+  labelChanged(newVal, oldVal) {
+    console.log(\`label changed. new:\${newVal}, old:\${oldVal}\`)
+  }
+}
+</script>
vue
<script lang="ts" setup>
+const props = withDefaults(defineProps<{
+  label?: string,
+  student?: ApplicationUserViewModel
+}>(), { label: 'Student' })
+
+watch(
+  () => props.label,
+  (newVal, oldVal) => {
+    console.log(\`label changed. new:\${newVal}, old:\${oldVal}\`);
+  }
+);
+</script>

Reactive data

vue
<script lang="ts">
+import { Vue, Component } from "vue-property-decorator";
+import { PersonViewModel } from "@/viewmodels.g";
+
+@Component({})
+export default class MyComponent extends Vue {
+  public person = new PersonViewModel();
+
+  public checked = false;
+
+  public items = [
+    { name: "Foo", checked: false, },
+    { name: "Bar", checked: true, }
+  ]
+}
+</script>
vue
<script lang="ts" setup>
+import { PersonViewModel } from "@/viewmodels.g";
+
+// Properties on coalesce-generated ViewModels have built in reactivity 
+// and don't need to be wrapped ref/reactive unless you're going to replace 
+// the entire top level object with a different instance.
+const person = new PersonViewModel();
+
+const checked = ref(false);
+
+const items = reactive([
+  { name: "Foo", checked: false, },
+  { name: "Bar", checked: true, }
+])
+</script>

Computed values

vue
<script lang="ts">
+import { Vue, Component } from "vue-property-decorator";
+import { PersonViewModel } from "@/viewmodels.g";
+
+@Component({})
+export default class MyComponent extends Vue {
+  public person = new PersonViewModel()
+
+  get fullName() {
+    return \`\${person.firstName} \${person.lastName}\`
+  }
+}
+</script>
vue
<script lang="ts" setup>
+import { PersonViewModel } from "@/viewmodels.g";
+
+const person = new PersonViewModel();
+
+const fullName = computed(() => \`\${person.firstName} \${person.lastName}\`)
+</script>

$emit, methods

vue
<template>
+  <input
+    :value="value"
+    @input="inputChanged($event.target.value)"
+  />
+</template>
+
+<script lang="ts">
+import { Vue, Component } from "vue-property-decorator";
+
+@Component({})
+export default class MyComponent extends Vue {
+  @Prop()
+  value!: string;
+
+  inputChanged(v: string) {
+    this.$emit('update:input', v)
+  }
+}
+</script>
vue
<template>
+  <input
+    :value="modelValue"
+    @input="inputChanged(($event.target as HTMLInputElement).value)"
+  />
+</template>
+
+<script lang="ts" setup>
+defineProps<{ modelValue: string | null }>();
+
+// This may seem tedious, but it enables full Typescript intellisense!
+const emit = defineEmits<{
+  (e: "update:modelValue", value: string | null): void;
+}>();
+
+function inputChanged(v: string) {
+  emit('update:modelValue', v)
+}
+</script>
`,14),e=[o];function t(c,r,D,y,i,C){return n(),a("div",null,e)}const g=s(p,[["render",t]]);export{d as __pageData,g as default}; diff --git a/assets/stacks_vue_vue2-to-vue3.md.hLyUCPCI.lean.js b/assets/stacks_vue_vue2-to-vue3.md.hLyUCPCI.lean.js new file mode 100644 index 000000000..63b21799c --- /dev/null +++ b/assets/stacks_vue_vue2-to-vue3.md.hLyUCPCI.lean.js @@ -0,0 +1 @@ +import{_ as s,o as n,c as a,R as l}from"./chunks/framework.g9eZ-ZSs.js";const d=JSON.parse('{"title":"Vue 2 to Vue 3","description":"","frontmatter":{"pageClass":"wide-page"},"headers":[],"relativePath":"stacks/vue/vue2-to-vue3.md","filePath":"stacks/vue/vue2-to-vue3.md"}'),p={name:"stacks/vue/vue2-to-vue3.md"},o=l("",14),e=[o];function t(c,r,D,y,i,C){return n(),a("div",null,e)}const g=s(p,[["render",t]]);export{d as __pageData,g as default}; diff --git a/assets/style.SedhU3_x.css b/assets/style.SedhU3_x.css new file mode 100644 index 000000000..28e6f4a05 --- /dev/null +++ b/assets/style.SedhU3_x.css @@ -0,0 +1 @@ +@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:normal;font-named-instance:"Regular";src:url(/Coalesce/assets/inter-roman-cyrillic.jIZ9REo5.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:normal;font-named-instance:"Regular";src:url(/Coalesce/assets/inter-roman-cyrillic-ext.8T9wMG5w.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:normal;font-named-instance:"Regular";src:url(/Coalesce/assets/inter-roman-greek.Cb5wWeGA.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:normal;font-named-instance:"Regular";src:url(/Coalesce/assets/inter-roman-greek-ext.9JiNzaSO.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:normal;font-named-instance:"Regular";src:url(/Coalesce/assets/inter-roman-latin.bvIUbFQP.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:normal;font-named-instance:"Regular";src:url(/Coalesce/assets/inter-roman-latin-ext.GZWE-KO4.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:normal;font-named-instance:"Regular";src:url(/Coalesce/assets/inter-roman-vietnamese.paY3CzEB.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:italic;font-named-instance:"Italic";src:url(/Coalesce/assets/inter-italic-cyrillic.-nLMcIwj.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:italic;font-named-instance:"Italic";src:url(/Coalesce/assets/inter-italic-cyrillic-ext.OVycGSDq.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:italic;font-named-instance:"Italic";src:url(/Coalesce/assets/inter-italic-greek.PSfer2Kc.woff2) format("woff2");unicode-range:U+0370-03FF}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:italic;font-named-instance:"Italic";src:url(/Coalesce/assets/inter-italic-greek-ext.hznxWNZO.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:italic;font-named-instance:"Italic";src:url(/Coalesce/assets/inter-italic-latin.27E69YJn.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:italic;font-named-instance:"Italic";src:url(/Coalesce/assets/inter-italic-latin-ext.RnFly65-.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter var;font-weight:100 900;font-display:swap;font-style:italic;font-named-instance:"Italic";src:url(/Coalesce/assets/inter-italic-vietnamese.xzQHe1q1.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+1EA0-1EF9,U+20AB}@font-face{font-family:Chinese Quotes;src:local("PingFang SC Regular"),local("PingFang SC"),local("SimHei"),local("Source Han Sans SC");unicode-range:U+2018,U+2019,U+201C,U+201D}:root{--vp-c-white: #ffffff;--vp-c-black: #000000;--vp-c-neutral: var(--vp-c-black);--vp-c-neutral-inverse: var(--vp-c-white)}.dark{--vp-c-neutral: var(--vp-c-white);--vp-c-neutral-inverse: var(--vp-c-black)}:root{--vp-c-gray-1: #dddde3;--vp-c-gray-2: #e4e4e9;--vp-c-gray-3: #ebebef;--vp-c-gray-soft: rgba(142, 150, 170, .14);--vp-c-indigo-1: #3451b2;--vp-c-indigo-2: #3a5ccc;--vp-c-indigo-3: #5672cd;--vp-c-indigo-soft: rgba(100, 108, 255, .14);--vp-c-green-1: #18794e;--vp-c-green-2: #299764;--vp-c-green-3: #30a46c;--vp-c-green-soft: rgba(16, 185, 129, .14);--vp-c-yellow-1: #915930;--vp-c-yellow-2: #946300;--vp-c-yellow-3: #9f6a00;--vp-c-yellow-soft: rgba(234, 179, 8, .14);--vp-c-red-1: #b8272c;--vp-c-red-2: #d5393e;--vp-c-red-3: #e0575b;--vp-c-red-soft: rgba(244, 63, 94, .14);--vp-c-sponsor: #db2777}.dark{--vp-c-gray-1: #515c67;--vp-c-gray-2: #414853;--vp-c-gray-3: #32363f;--vp-c-gray-soft: rgba(101, 117, 133, .16);--vp-c-indigo-1: #a8b1ff;--vp-c-indigo-2: #5c73e7;--vp-c-indigo-3: #3e63dd;--vp-c-indigo-soft: rgba(100, 108, 255, .16);--vp-c-green-1: #3dd68c;--vp-c-green-2: #30a46c;--vp-c-green-3: #298459;--vp-c-green-soft: rgba(16, 185, 129, .16);--vp-c-yellow-1: #f9b44e;--vp-c-yellow-2: #da8b17;--vp-c-yellow-3: #a46a0a;--vp-c-yellow-soft: rgba(234, 179, 8, .16);--vp-c-red-1: #f66f81;--vp-c-red-2: #f14158;--vp-c-red-3: #b62a3c;--vp-c-red-soft: rgba(244, 63, 94, .16)}:root{--vp-c-bg: #ffffff;--vp-c-bg-alt: #f6f6f7;--vp-c-bg-elv: #ffffff;--vp-c-bg-soft: #f6f6f7}.dark{--vp-c-bg: #1b1b1f;--vp-c-bg-alt: #161618;--vp-c-bg-elv: #202127;--vp-c-bg-soft: #202127}:root{--vp-c-border: #c2c2c4;--vp-c-divider: #e2e2e3;--vp-c-gutter: #e2e2e3}.dark{--vp-c-border: #3c3f44;--vp-c-divider: #2e2e32;--vp-c-gutter: #000000}:root{--vp-c-text-1: rgba(60, 60, 67);--vp-c-text-2: rgba(60, 60, 67, .78);--vp-c-text-3: rgba(60, 60, 67, .56)}.dark{--vp-c-text-1: rgba(255, 255, 245, .86);--vp-c-text-2: rgba(235, 235, 245, .6);--vp-c-text-3: rgba(235, 235, 245, .38)}:root{--vp-c-default-1: var(--vp-c-gray-1);--vp-c-default-2: var(--vp-c-gray-2);--vp-c-default-3: var(--vp-c-gray-3);--vp-c-default-soft: var(--vp-c-gray-soft);--vp-c-brand-1: var(--vp-c-indigo-1);--vp-c-brand-2: var(--vp-c-indigo-2);--vp-c-brand-3: var(--vp-c-indigo-3);--vp-c-brand-soft: var(--vp-c-indigo-soft);--vp-c-brand: var(--vp-c-brand-1);--vp-c-tip-1: var(--vp-c-brand-1);--vp-c-tip-2: var(--vp-c-brand-2);--vp-c-tip-3: var(--vp-c-brand-3);--vp-c-tip-soft: var(--vp-c-brand-soft);--vp-c-warning-1: var(--vp-c-yellow-1);--vp-c-warning-2: var(--vp-c-yellow-2);--vp-c-warning-3: var(--vp-c-yellow-3);--vp-c-warning-soft: var(--vp-c-yellow-soft);--vp-c-danger-1: var(--vp-c-red-1);--vp-c-danger-2: var(--vp-c-red-2);--vp-c-danger-3: var(--vp-c-red-3);--vp-c-danger-soft: var(--vp-c-red-soft)}:root{--vp-font-family-base: "Chinese Quotes", "Inter var", "Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--vp-font-family-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}:root{--vp-shadow-1: 0 1px 2px rgba(0, 0, 0, .04), 0 1px 2px rgba(0, 0, 0, .06);--vp-shadow-2: 0 3px 12px rgba(0, 0, 0, .07), 0 1px 4px rgba(0, 0, 0, .07);--vp-shadow-3: 0 12px 32px rgba(0, 0, 0, .1), 0 2px 6px rgba(0, 0, 0, .08);--vp-shadow-4: 0 14px 44px rgba(0, 0, 0, .12), 0 3px 9px rgba(0, 0, 0, .12);--vp-shadow-5: 0 18px 56px rgba(0, 0, 0, .16), 0 4px 12px rgba(0, 0, 0, .16)}:root{--vp-z-index-footer: 10;--vp-z-index-local-nav: 20;--vp-z-index-nav: 30;--vp-z-index-layout-top: 40;--vp-z-index-backdrop: 50;--vp-z-index-sidebar: 60}:root{--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E");--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E")}:root{--vp-layout-max-width: 1440px}:root{--vp-header-anchor-symbol: "#"}:root{--vp-code-line-height: 1.7;--vp-code-font-size: .875em;--vp-code-color: var(--vp-c-brand-1);--vp-code-link-color: var(--vp-c-brand-1);--vp-code-link-hover-color: var(--vp-c-brand-2);--vp-code-bg: var(--vp-c-default-soft);--vp-code-block-color: var(--vp-c-text-2);--vp-code-block-bg: var(--vp-c-bg-alt);--vp-code-block-divider-color: var(--vp-c-gutter);--vp-code-lang-color: var(--vp-c-text-3);--vp-code-line-highlight-color: var(--vp-c-default-soft);--vp-code-line-number-color: var(--vp-c-text-3);--vp-code-line-diff-add-color: var(--vp-c-green-soft);--vp-code-line-diff-add-symbol-color: var(--vp-c-green-1);--vp-code-line-diff-remove-color: var(--vp-c-red-soft);--vp-code-line-diff-remove-symbol-color: var(--vp-c-red-1);--vp-code-line-warning-color: var(--vp-c-yellow-soft);--vp-code-line-error-color: var(--vp-c-red-soft);--vp-code-copy-code-border-color: var(--vp-c-divider);--vp-code-copy-code-bg: var(--vp-c-bg-soft);--vp-code-copy-code-hover-border-color: var(--vp-c-divider);--vp-code-copy-code-hover-bg: var(--vp-c-bg);--vp-code-copy-code-active-text: var(--vp-c-text-2);--vp-code-copy-copied-text-content: "Copied";--vp-code-tab-divider: var(--vp-code-block-divider-color);--vp-code-tab-text-color: var(--vp-c-text-2);--vp-code-tab-bg: var(--vp-code-block-bg);--vp-code-tab-hover-text-color: var(--vp-c-text-1);--vp-code-tab-active-text-color: var(--vp-c-text-1);--vp-code-tab-active-bar-color: var(--vp-c-brand-1)}:root{--vp-button-brand-border: transparent;--vp-button-brand-text: var(--vp-c-white);--vp-button-brand-bg: var(--vp-c-brand-3);--vp-button-brand-hover-border: transparent;--vp-button-brand-hover-text: var(--vp-c-white);--vp-button-brand-hover-bg: var(--vp-c-brand-2);--vp-button-brand-active-border: transparent;--vp-button-brand-active-text: var(--vp-c-white);--vp-button-brand-active-bg: var(--vp-c-brand-1);--vp-button-alt-border: transparent;--vp-button-alt-text: var(--vp-c-text-1);--vp-button-alt-bg: var(--vp-c-default-3);--vp-button-alt-hover-border: transparent;--vp-button-alt-hover-text: var(--vp-c-text-1);--vp-button-alt-hover-bg: var(--vp-c-default-2);--vp-button-alt-active-border: transparent;--vp-button-alt-active-text: var(--vp-c-text-1);--vp-button-alt-active-bg: var(--vp-c-default-1);--vp-button-sponsor-border: var(--vp-c-text-2);--vp-button-sponsor-text: var(--vp-c-text-2);--vp-button-sponsor-bg: transparent;--vp-button-sponsor-hover-border: var(--vp-c-sponsor);--vp-button-sponsor-hover-text: var(--vp-c-sponsor);--vp-button-sponsor-hover-bg: transparent;--vp-button-sponsor-active-border: var(--vp-c-sponsor);--vp-button-sponsor-active-text: var(--vp-c-sponsor);--vp-button-sponsor-active-bg: transparent}:root{--vp-custom-block-font-size: 14px;--vp-custom-block-code-font-size: 13px;--vp-custom-block-info-border: transparent;--vp-custom-block-info-text: var(--vp-c-text-1);--vp-custom-block-info-bg: var(--vp-c-default-soft);--vp-custom-block-info-code-bg: var(--vp-c-default-soft);--vp-custom-block-tip-border: transparent;--vp-custom-block-tip-text: var(--vp-c-text-1);--vp-custom-block-tip-bg: var(--vp-c-brand-soft);--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);--vp-custom-block-warning-border: transparent;--vp-custom-block-warning-text: var(--vp-c-text-1);--vp-custom-block-warning-bg: var(--vp-c-warning-soft);--vp-custom-block-warning-code-bg: var(--vp-c-warning-soft);--vp-custom-block-danger-border: transparent;--vp-custom-block-danger-text: var(--vp-c-text-1);--vp-custom-block-danger-bg: var(--vp-c-danger-soft);--vp-custom-block-danger-code-bg: var(--vp-c-danger-soft);--vp-custom-block-details-border: var(--vp-custom-block-info-border);--vp-custom-block-details-text: var(--vp-custom-block-info-text);--vp-custom-block-details-bg: var(--vp-custom-block-info-bg);--vp-custom-block-details-code-bg: var(--vp-custom-block-info-code-bg)}:root{--vp-input-border-color: var(--vp-c-border);--vp-input-bg-color: var(--vp-c-bg-alt);--vp-input-switch-bg-color: var(--vp-c-gray-soft)}:root{--vp-nav-height: 64px;--vp-nav-bg-color: var(--vp-c-bg);--vp-nav-screen-bg-color: var(--vp-c-bg);--vp-nav-logo-height: 24px}.hide-nav{--vp-nav-height: 0px}.hide-nav .VPSidebar{--vp-nav-height: 22px}:root{--vp-local-nav-bg-color: var(--vp-c-bg)}:root{--vp-sidebar-width: 272px;--vp-sidebar-bg-color: var(--vp-c-bg-alt)}:root{--vp-backdrop-bg-color: rgba(0, 0, 0, .6)}:root{--vp-home-hero-name-color: var(--vp-c-brand-1);--vp-home-hero-name-background: transparent;--vp-home-hero-image-background-image: none;--vp-home-hero-image-filter: none}:root{--vp-badge-info-border: transparent;--vp-badge-info-text: var(--vp-c-text-2);--vp-badge-info-bg: var(--vp-c-default-soft);--vp-badge-tip-border: transparent;--vp-badge-tip-text: var(--vp-c-brand-1);--vp-badge-tip-bg: var(--vp-c-brand-soft);--vp-badge-warning-border: transparent;--vp-badge-warning-text: var(--vp-c-warning-1);--vp-badge-warning-bg: var(--vp-c-warning-soft);--vp-badge-danger-border: transparent;--vp-badge-danger-text: var(--vp-c-danger-1);--vp-badge-danger-bg: var(--vp-c-danger-soft)}:root{--vp-carbon-ads-text-color: var(--vp-c-text-1);--vp-carbon-ads-poweredby-color: var(--vp-c-text-2);--vp-carbon-ads-bg-color: var(--vp-c-bg-soft);--vp-carbon-ads-hover-text-color: var(--vp-c-brand-1);--vp-carbon-ads-hover-poweredby-color: var(--vp-c-text-1)}:root{--vp-local-search-bg: var(--vp-c-bg);--vp-local-search-result-bg: var(--vp-c-bg);--vp-local-search-result-border: var(--vp-c-divider);--vp-local-search-result-selected-bg: var(--vp-c-bg);--vp-local-search-result-selected-border: var(--vp-c-brand-1);--vp-local-search-highlight-bg: var(--vp-c-brand-1);--vp-local-search-highlight-text: var(--vp-c-neutral-inverse)}@media (prefers-reduced-motion: reduce){*,:before,:after{animation-delay:-1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important;background-attachment:initial!important;scroll-behavior:auto!important;transition-duration:0s!important;transition-delay:0s!important}}*,:before,:after{box-sizing:border-box}html{line-height:1.4;font-size:16px;-webkit-text-size-adjust:100%}html.dark{color-scheme:dark}body{margin:0;width:100%;min-width:320px;min-height:100vh;line-height:24px;font-family:var(--vp-font-family-base);font-size:16px;font-weight:400;color:var(--vp-c-text-1);background-color:var(--vp-c-bg);direction:ltr;font-synthesis:style;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}main{display:block}h1,h2,h3,h4,h5,h6{margin:0;line-height:24px;font-size:16px;font-weight:400}p{margin:0}strong,b{font-weight:600}a,area,button,[role=button],input,label,select,summary,textarea{touch-action:manipulation}a{color:inherit;text-decoration:inherit}ol,ul{list-style:none;margin:0;padding:0}blockquote{margin:0}pre,code,kbd,samp{font-family:var(--vp-font-family-mono)}img,svg,video,canvas,audio,iframe,embed,object{display:block}figure{margin:0}img,video{max-width:100%;height:auto}button,input,optgroup,select,textarea{border:0;padding:0;line-height:inherit;color:inherit}button{padding:0;font-family:inherit;background-color:transparent;background-image:none}button:enabled,[role=button]:enabled{cursor:pointer}button:focus,button:focus-visible{outline:1px dotted;outline:4px auto -webkit-focus-ring-color}button:focus:not(:focus-visible){outline:none!important}input:focus,textarea:focus,select:focus{outline:none}table{border-collapse:collapse}input{background-color:transparent}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:var(--vp-c-text-3)}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:var(--vp-c-text-3)}input::placeholder,textarea::placeholder{color:var(--vp-c-text-3)}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield}textarea{resize:vertical}select{-webkit-appearance:none}fieldset{margin:0;padding:0}h1,h2,h3,h4,h5,h6,li,p{overflow-wrap:break-word}vite-error-overlay{z-index:9999}mjx-container{display:inline-block;margin:auto 2px -2px}mjx-container>svg{margin:auto}.visually-hidden{position:absolute;width:1px;height:1px;white-space:nowrap;clip:rect(0 0 0 0);clip-path:inset(50%);overflow:hidden}.custom-block{border:1px solid transparent;border-radius:8px;padding:16px 16px 8px;line-height:24px;font-size:var(--vp-custom-block-font-size);color:var(--vp-c-text-2)}.custom-block.info{border-color:var(--vp-custom-block-info-border);color:var(--vp-custom-block-info-text);background-color:var(--vp-custom-block-info-bg)}.custom-block.info a,.custom-block.info code{color:var(--vp-c-brand-1)}.custom-block.info a:hover{color:var(--vp-c-brand-2)}.custom-block.info code{background-color:var(--vp-custom-block-info-code-bg)}.custom-block.tip{border-color:var(--vp-custom-block-tip-border);color:var(--vp-custom-block-tip-text);background-color:var(--vp-custom-block-tip-bg)}.custom-block.tip a,.custom-block.tip code{color:var(--vp-c-brand-1)}.custom-block.tip a:hover{color:var(--vp-c-brand-2)}.custom-block.tip code{background-color:var(--vp-custom-block-tip-code-bg)}.custom-block.warning{border-color:var(--vp-custom-block-warning-border);color:var(--vp-custom-block-warning-text);background-color:var(--vp-custom-block-warning-bg)}.custom-block.warning a,.custom-block.warning code{color:var(--vp-c-warning-1)}.custom-block.warning a:hover{color:var(--vp-c-warning-2)}.custom-block.warning code{background-color:var(--vp-custom-block-warning-code-bg)}.custom-block.danger{border-color:var(--vp-custom-block-danger-border);color:var(--vp-custom-block-danger-text);background-color:var(--vp-custom-block-danger-bg)}.custom-block.danger a,.custom-block.danger code{color:var(--vp-c-danger-1)}.custom-block.danger a:hover{color:var(--vp-c-danger-2)}.custom-block.danger code{background-color:var(--vp-custom-block-danger-code-bg)}.custom-block.details{border-color:var(--vp-custom-block-details-border);color:var(--vp-custom-block-details-text);background-color:var(--vp-custom-block-details-bg)}.custom-block.details a{color:var(--vp-c-brand-1)}.custom-block.details a:hover{color:var(--vp-c-brand-2)}.custom-block.details code{background-color:var(--vp-custom-block-details-code-bg)}.custom-block-title{font-weight:600}.custom-block p+p{margin:8px 0}.custom-block.details summary{margin:0 0 8px;font-weight:700;cursor:pointer}.custom-block.details summary+p{margin:8px 0}.custom-block a{color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px;transition:opacity .25s}.custom-block a:hover{opacity:.75}.custom-block code{font-size:var(--vp-custom-block-code-font-size)}.custom-block.custom-block th,.custom-block.custom-block blockquote>p{font-size:var(--vp-custom-block-font-size);color:inherit}.dark .vp-code span{color:var(--shiki-dark, inherit)}html:not(.dark) .vp-code span{color:var(--shiki-light, inherit)}.vp-code-group{margin-top:16px}.vp-code-group .tabs{position:relative;display:flex;margin-right:-24px;margin-left:-24px;padding:0 12px;background-color:var(--vp-code-tab-bg);overflow-x:auto;overflow-y:hidden;box-shadow:inset 0 -1px var(--vp-code-tab-divider)}@media (min-width: 640px){.vp-code-group .tabs{margin-right:0;margin-left:0;border-radius:8px 8px 0 0}}.vp-code-group .tabs input{position:fixed;opacity:0;pointer-events:none}.vp-code-group .tabs label{position:relative;display:inline-block;border-bottom:1px solid transparent;padding:0 12px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-code-tab-text-color);white-space:nowrap;cursor:pointer;transition:color .25s}.vp-code-group .tabs label:after{position:absolute;right:8px;bottom:-1px;left:8px;z-index:1;height:2px;border-radius:2px;content:"";background-color:transparent;transition:background-color .25s}.vp-code-group label:hover{color:var(--vp-code-tab-hover-text-color)}.vp-code-group input:checked+label{color:var(--vp-code-tab-active-text-color)}.vp-code-group input:checked+label:after{background-color:var(--vp-code-tab-active-bar-color)}.vp-code-group div[class*=language-],.vp-block{display:none;margin-top:0!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.vp-code-group div[class*=language-].active,.vp-block.active{display:block}.vp-block{padding:20px 24px}.vp-doc h1,.vp-doc h2,.vp-doc h3,.vp-doc h4,.vp-doc h5,.vp-doc h6{position:relative;font-weight:600;outline:none}.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:28px}.vp-doc h2{margin:48px 0 16px;border-top:1px solid var(--vp-c-divider);padding-top:24px;letter-spacing:-.02em;line-height:32px;font-size:24px}.vp-doc h3{margin:32px 0 0;letter-spacing:-.01em;line-height:28px;font-size:20px}.vp-doc .header-anchor{position:absolute;top:0;left:0;margin-left:-.87em;font-weight:500;-webkit-user-select:none;user-select:none;opacity:0;text-decoration:none;transition:color .25s,opacity .25s}.vp-doc .header-anchor:before{content:var(--vp-header-anchor-symbol)}.vp-doc h1:hover .header-anchor,.vp-doc h1 .header-anchor:focus,.vp-doc h2:hover .header-anchor,.vp-doc h2 .header-anchor:focus,.vp-doc h3:hover .header-anchor,.vp-doc h3 .header-anchor:focus,.vp-doc h4:hover .header-anchor,.vp-doc h4 .header-anchor:focus,.vp-doc h5:hover .header-anchor,.vp-doc h5 .header-anchor:focus,.vp-doc h6:hover .header-anchor,.vp-doc h6 .header-anchor:focus{opacity:1}@media (min-width: 768px){.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:32px}}.vp-doc h2 .header-anchor{top:24px}.vp-doc p,.vp-doc summary{margin:16px 0}.vp-doc p{line-height:28px}.vp-doc blockquote{margin:16px 0;border-left:2px solid var(--vp-c-divider);padding-left:16px;transition:border-color .5s}.vp-doc blockquote>p{margin:0;font-size:16px;color:var(--vp-c-text-2);transition:color .5s}.vp-doc a{font-weight:500;color:var(--vp-c-brand-1);text-decoration:underline;text-underline-offset:2px;transition:color .25s,opacity .25s}.vp-doc a:hover{color:var(--vp-c-brand-2)}.vp-doc strong{font-weight:600}.vp-doc ul,.vp-doc ol{padding-left:1.25rem;margin:16px 0}.vp-doc ul{list-style:disc}.vp-doc ol{list-style:decimal}.vp-doc li+li{margin-top:8px}.vp-doc li>ol,.vp-doc li>ul{margin:8px 0 0}.vp-doc table{display:block;border-collapse:collapse;margin:20px 0;overflow-x:auto}.vp-doc tr{background-color:var(--vp-c-bg);border-top:1px solid var(--vp-c-divider);transition:background-color .5s}.vp-doc tr:nth-child(2n){background-color:var(--vp-c-bg-soft)}.vp-doc th,.vp-doc td{border:1px solid var(--vp-c-divider);padding:8px 16px}.vp-doc th{text-align:left;font-size:14px;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-doc td{font-size:14px}.vp-doc hr{margin:16px 0;border:none;border-top:1px solid var(--vp-c-divider)}.vp-doc .custom-block{margin:16px 0}.vp-doc .custom-block p{margin:8px 0;line-height:24px}.vp-doc .custom-block p:first-child{margin:0}.vp-doc .custom-block div[class*=language-]{margin:8px 0;border-radius:8px}.vp-doc .custom-block div[class*=language-] code{font-weight:400;background-color:transparent}.vp-doc .custom-block .vp-code-group .tabs{margin:0;border-radius:8px 8px 0 0}.vp-doc :not(pre,h1,h2,h3,h4,h5,h6)>code{font-size:var(--vp-code-font-size);color:var(--vp-code-color)}.vp-doc :not(pre)>code{border-radius:4px;padding:3px 6px;background-color:var(--vp-code-bg);transition:color .25s,background-color .5s}.vp-doc a>code{color:var(--vp-code-link-color)}.vp-doc a:hover>code{color:var(--vp-code-link-hover-color)}.vp-doc h1>code,.vp-doc h2>code,.vp-doc h3>code{font-size:.9em}.vp-doc div[class*=language-],.vp-block{position:relative;margin:16px -24px;background-color:var(--vp-code-block-bg);overflow-x:auto;transition:background-color .5s}@media (min-width: 640px){.vp-doc div[class*=language-],.vp-block{border-radius:8px;margin:16px 0}}@media (max-width: 639px){.vp-doc li div[class*=language-]{border-radius:8px 0 0 8px}}.vp-doc div[class*=language-]+div[class*=language-],.vp-doc div[class$=-api]+div[class*=language-],.vp-doc div[class*=language-]+div[class$=-api]>div[class*=language-]{margin-top:-8px}.vp-doc [class*=language-] pre,.vp-doc [class*=language-] code{direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}.vp-doc [class*=language-] pre{position:relative;z-index:1;margin:0;padding:20px 0;background:transparent;overflow-x:auto}.vp-doc [class*=language-] code{display:block;padding:0 24px;width:fit-content;min-width:100%;line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-block-color);transition:color .5s}.vp-doc [class*=language-] code .highlighted{background-color:var(--vp-code-line-highlight-color);transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .highlighted.error{background-color:var(--vp-code-line-error-color)}.vp-doc [class*=language-] code .highlighted.warning{background-color:var(--vp-code-line-warning-color)}.vp-doc [class*=language-] code .diff{transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .diff:before{position:absolute;left:10px}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){filter:blur(.095rem);opacity:.4;transition:filter .35s,opacity .35s}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){opacity:.7;transition:filter .35s,opacity .35s}.vp-doc [class*=language-]:hover .has-focused-lines .line:not(.has-focus){filter:blur(0);opacity:1}.vp-doc [class*=language-] code .diff.remove{background-color:var(--vp-code-line-diff-remove-color);opacity:.7}.vp-doc [class*=language-] code .diff.remove:before{content:"-";color:var(--vp-code-line-diff-remove-symbol-color)}.vp-doc [class*=language-] code .diff.add{background-color:var(--vp-code-line-diff-add-color)}.vp-doc [class*=language-] code .diff.add:before{content:"+";color:var(--vp-code-line-diff-add-symbol-color)}.vp-doc div[class*=language-].line-numbers-mode{padding-left:32px}.vp-doc .line-numbers-wrapper{position:absolute;top:0;bottom:0;left:0;z-index:3;border-right:1px solid var(--vp-code-block-divider-color);padding-top:20px;width:32px;text-align:center;font-family:var(--vp-font-family-mono);line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-line-number-color);transition:border-color .5s,color .5s}.vp-doc [class*=language-]>button.copy{direction:ltr;position:absolute;top:12px;right:12px;z-index:3;border:1px solid var(--vp-code-copy-code-border-color);border-radius:4px;width:40px;height:40px;background-color:var(--vp-code-copy-code-bg);opacity:0;cursor:pointer;background-image:var(--vp-icon-copy);background-position:50%;background-size:20px;background-repeat:no-repeat;transition:border-color .25s,background-color .25s,opacity .25s}.vp-doc [class*=language-]:hover>button.copy,.vp-doc [class*=language-]>button.copy:focus{opacity:1}.vp-doc [class*=language-]>button.copy:hover,.vp-doc [class*=language-]>button.copy.copied{border-color:var(--vp-code-copy-code-hover-border-color);background-color:var(--vp-code-copy-code-hover-bg)}.vp-doc [class*=language-]>button.copy.copied,.vp-doc [class*=language-]>button.copy:hover.copied{border-radius:0 4px 4px 0;background-color:var(--vp-code-copy-code-hover-bg);background-image:var(--vp-icon-copied)}.vp-doc [class*=language-]>button.copy.copied:before,.vp-doc [class*=language-]>button.copy:hover.copied:before{position:relative;top:-1px;transform:translate(calc(-100% - 1px));display:flex;justify-content:center;align-items:center;border:1px solid var(--vp-code-copy-code-hover-border-color);border-right:0;border-radius:4px 0 0 4px;padding:0 10px;width:fit-content;height:40px;text-align:center;font-size:12px;font-weight:500;color:var(--vp-code-copy-code-active-text);background-color:var(--vp-code-copy-code-hover-bg);white-space:nowrap;content:var(--vp-code-copy-copied-text-content)}.vp-doc [class*=language-]>span.lang{position:absolute;top:2px;right:8px;z-index:2;font-size:12px;font-weight:500;color:var(--vp-code-lang-color);transition:color .4s,opacity .4s}.vp-doc [class*=language-]:hover>button.copy+span.lang,.vp-doc [class*=language-]>button.copy:focus+span.lang{opacity:0}.vp-doc .VPTeamMembers{margin-top:24px}.vp-doc .VPTeamMembers.small.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}.vp-doc .VPTeamMembers.small.count-2 .container,.vp-doc .VPTeamMembers.small.count-3 .container{max-width:100%!important}.vp-doc .VPTeamMembers.medium.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}:is(.vp-external-link-icon,.vp-doc a[href*="://"],.vp-doc a[target=_blank]):not(.no-icon):after{display:inline-block;margin-top:-1px;margin-left:4px;width:11px;height:11px;background:currentColor;color:var(--vp-c-text-3);flex-shrink:0;--icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");-webkit-mask-image:var(--icon);mask-image:var(--icon)}.vp-external-link-icon:after{content:""}.vp-sponsor{border-radius:16px;overflow:hidden}.vp-sponsor.aside{border-radius:12px}.vp-sponsor-section+.vp-sponsor-section{margin-top:4px}.vp-sponsor-tier{margin-bottom:4px;text-align:center;letter-spacing:1px;line-height:24px;width:100%;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-sponsor.normal .vp-sponsor-tier{padding:13px 0 11px;font-size:14px}.vp-sponsor.aside .vp-sponsor-tier{padding:9px 0 7px;font-size:12px}.vp-sponsor-grid+.vp-sponsor-tier{margin-top:4px}.vp-sponsor-grid{display:flex;flex-wrap:wrap;gap:4px}.vp-sponsor-grid.xmini .vp-sponsor-grid-link{height:64px}.vp-sponsor-grid.xmini .vp-sponsor-grid-image{max-width:64px;max-height:22px}.vp-sponsor-grid.mini .vp-sponsor-grid-link{height:72px}.vp-sponsor-grid.mini .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.small .vp-sponsor-grid-link{height:96px}.vp-sponsor-grid.small .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.medium .vp-sponsor-grid-link{height:112px}.vp-sponsor-grid.medium .vp-sponsor-grid-image{max-width:120px;max-height:36px}.vp-sponsor-grid.big .vp-sponsor-grid-link{height:184px}.vp-sponsor-grid.big .vp-sponsor-grid-image{max-width:192px;max-height:56px}.vp-sponsor-grid[data-vp-grid="2"] .vp-sponsor-grid-item{width:calc((100% - 4px)/2)}.vp-sponsor-grid[data-vp-grid="3"] .vp-sponsor-grid-item{width:calc((100% - 4px * 2) / 3)}.vp-sponsor-grid[data-vp-grid="4"] .vp-sponsor-grid-item{width:calc((100% - 12px)/4)}.vp-sponsor-grid[data-vp-grid="5"] .vp-sponsor-grid-item{width:calc((100% - 16px)/5)}.vp-sponsor-grid[data-vp-grid="6"] .vp-sponsor-grid-item{width:calc((100% - 4px * 5) / 6)}.vp-sponsor-grid-item{flex-shrink:0;width:100%;background-color:var(--vp-c-bg-soft);transition:background-color .25s}.vp-sponsor-grid-item:hover{background-color:var(--vp-c-default-soft)}.vp-sponsor-grid-item:hover .vp-sponsor-grid-image{filter:grayscale(0) invert(0)}.vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.dark .vp-sponsor-grid-item:hover{background-color:var(--vp-c-white)}.dark .vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.vp-sponsor-grid-link{display:flex}.vp-sponsor-grid-box{display:flex;justify-content:center;align-items:center;width:100%}.vp-sponsor-grid-image{max-width:100%;filter:grayscale(1);transition:filter .25s}.dark .vp-sponsor-grid-image{filter:grayscale(1) invert(1)}.VPBadge[data-v-ea5b2908]{display:inline-block;margin-left:2px;border:1px solid transparent;border-radius:12px;padding:0 10px;line-height:22px;font-size:12px;font-weight:500;transform:translateY(-2px)}.vp-doc h1>.VPBadge[data-v-ea5b2908]{margin-top:4px;vertical-align:top}.vp-doc h2>.VPBadge[data-v-ea5b2908]{margin-top:3px;padding:0 8px;vertical-align:top}.vp-doc h3>.VPBadge[data-v-ea5b2908]{vertical-align:middle}.vp-doc h4>.VPBadge[data-v-ea5b2908],.vp-doc h5>.VPBadge[data-v-ea5b2908],.vp-doc h6>.VPBadge[data-v-ea5b2908]{vertical-align:middle;line-height:18px}.VPBadge.info[data-v-ea5b2908]{border-color:var(--vp-badge-info-border);color:var(--vp-badge-info-text);background-color:var(--vp-badge-info-bg)}.VPBadge.tip[data-v-ea5b2908]{border-color:var(--vp-badge-tip-border);color:var(--vp-badge-tip-text);background-color:var(--vp-badge-tip-bg)}.VPBadge.warning[data-v-ea5b2908]{border-color:var(--vp-badge-warning-border);color:var(--vp-badge-warning-text);background-color:var(--vp-badge-warning-bg)}.VPBadge.danger[data-v-ea5b2908]{border-color:var(--vp-badge-danger-border);color:var(--vp-badge-danger-text);background-color:var(--vp-badge-danger-bg)}.VPBackdrop[data-v-54a304ca]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:var(--vp-z-index-backdrop);background:var(--vp-backdrop-bg-color);transition:opacity .5s}.VPBackdrop.fade-enter-from[data-v-54a304ca],.VPBackdrop.fade-leave-to[data-v-54a304ca]{opacity:0}.VPBackdrop.fade-leave-active[data-v-54a304ca]{transition-duration:.25s}@media (min-width: 1280px){.VPBackdrop[data-v-54a304ca]{display:none}}.NotFound[data-v-b9c0c15a]{padding:64px 24px 96px;text-align:center}@media (min-width: 768px){.NotFound[data-v-b9c0c15a]{padding:96px 32px 168px}}.code[data-v-b9c0c15a]{line-height:64px;font-size:64px;font-weight:600}.title[data-v-b9c0c15a]{padding-top:12px;letter-spacing:2px;line-height:20px;font-size:20px;font-weight:700}.divider[data-v-b9c0c15a]{margin:24px auto 18px;width:64px;height:1px;background-color:var(--vp-c-divider)}.quote[data-v-b9c0c15a]{margin:0 auto;max-width:256px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.action[data-v-b9c0c15a]{padding-top:20px}.link[data-v-b9c0c15a]{display:inline-block;border:1px solid var(--vp-c-brand-1);border-radius:16px;padding:3px 16px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:border-color .25s,color .25s}.link[data-v-b9c0c15a]:hover{border-color:var(--vp-c-brand-2);color:var(--vp-c-brand-2)}.root[data-v-463da30f]{position:relative;z-index:1}.nested[data-v-463da30f]{padding-left:16px}.outline-link[data-v-463da30f]{display:block;line-height:28px;color:var(--vp-c-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .5s;font-weight:400}.outline-link[data-v-463da30f]:hover,.outline-link.active[data-v-463da30f]{color:var(--vp-c-text-1);transition:color .25s}.outline-link.nested[data-v-463da30f]{padding-left:13px}.VPDocAsideOutline[data-v-3a6c4994]{display:none}.VPDocAsideOutline.has-outline[data-v-3a6c4994]{display:block}.content[data-v-3a6c4994]{position:relative;border-left:1px solid var(--vp-c-divider);padding-left:16px;font-size:13px;font-weight:500}.outline-marker[data-v-3a6c4994]{position:absolute;top:32px;left:-1px;z-index:0;opacity:0;width:2px;border-radius:2px;height:18px;background-color:var(--vp-c-brand-1);transition:top .25s cubic-bezier(0,1,.5,1),background-color .5s,opacity .25s}.outline-title[data-v-3a6c4994]{letter-spacing:.4px;line-height:28px;font-size:13px;font-weight:600}.VPDocAside[data-v-cb998dce]{display:flex;flex-direction:column;flex-grow:1}.spacer[data-v-cb998dce]{flex-grow:1}.VPDocAside[data-v-cb998dce] .spacer+.VPDocAsideSponsors,.VPDocAside[data-v-cb998dce] .spacer+.VPDocAsideCarbonAds{margin-top:24px}.VPDocAside[data-v-cb998dce] .VPDocAsideSponsors+.VPDocAsideCarbonAds{margin-top:16px}.VPLastUpdated[data-v-19a7ae4e]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}@media (min-width: 640px){.VPLastUpdated[data-v-19a7ae4e]{line-height:32px;font-size:14px;font-weight:500}}.VPDocFooter[data-v-b4b63abf]{margin-top:64px}.edit-info[data-v-b4b63abf]{padding-bottom:18px}@media (min-width: 640px){.edit-info[data-v-b4b63abf]{display:flex;justify-content:space-between;align-items:center;padding-bottom:14px}}.edit-link-button[data-v-b4b63abf]{display:flex;align-items:center;border:0;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.edit-link-button[data-v-b4b63abf]:hover{color:var(--vp-c-brand-2)}.edit-link-icon[data-v-b4b63abf]{margin-right:8px;width:14px;height:14px;fill:currentColor}.prev-next[data-v-b4b63abf]{border-top:1px solid var(--vp-c-divider);padding-top:24px;display:grid;grid-row-gap:8px}@media (min-width: 640px){.prev-next[data-v-b4b63abf]{grid-template-columns:repeat(2,1fr);grid-column-gap:16px}}.pager-link[data-v-b4b63abf]{display:block;border:1px solid var(--vp-c-divider);border-radius:8px;padding:11px 16px 13px;width:100%;height:100%;transition:border-color .25s}.pager-link[data-v-b4b63abf]:hover{border-color:var(--vp-c-brand-1)}.pager-link.next[data-v-b4b63abf]{margin-left:auto;text-align:right}.desc[data-v-b4b63abf]{display:block;line-height:20px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.title[data-v-b4b63abf]{display:block;line-height:20px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.VPDocOutlineDropdown[data-v-95bb0785]{margin-bottom:48px}.VPDocOutlineDropdown button[data-v-95bb0785]{display:block;font-size:14px;font-weight:500;line-height:24px;border:1px solid var(--vp-c-border);padding:4px 12px;color:var(--vp-c-text-2);background-color:var(--vp-c-default-soft);border-radius:8px;transition:color .5s}.VPDocOutlineDropdown button[data-v-95bb0785]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPDocOutlineDropdown button.open[data-v-95bb0785]{color:var(--vp-c-text-1)}.icon[data-v-95bb0785]{display:inline-block;vertical-align:middle;width:16px;height:16px;fill:currentColor}[data-v-95bb0785] .outline-link{font-size:14px;font-weight:400}.open>.icon[data-v-95bb0785]{transform:rotate(90deg)}.items[data-v-95bb0785]{margin-top:12px;border-left:1px solid var(--vp-c-divider)}.VPDoc[data-v-a3c25e27]{padding:32px 24px 96px;width:100%}.VPDoc .VPDocOutlineDropdown[data-v-a3c25e27]{display:none}@media (min-width: 960px) and (max-width: 1279px){.VPDoc .VPDocOutlineDropdown[data-v-a3c25e27]{display:block}}@media (min-width: 768px){.VPDoc[data-v-a3c25e27]{padding:48px 32px 128px}}@media (min-width: 960px){.VPDoc[data-v-a3c25e27]{padding:32px 32px 0}.VPDoc:not(.has-sidebar) .container[data-v-a3c25e27]{display:flex;justify-content:center;max-width:992px}.VPDoc:not(.has-sidebar) .content[data-v-a3c25e27]{max-width:752px}}@media (min-width: 1280px){.VPDoc .container[data-v-a3c25e27]{display:flex;justify-content:center}.VPDoc .aside[data-v-a3c25e27]{display:block}}@media (min-width: 1440px){.VPDoc:not(.has-sidebar) .content[data-v-a3c25e27]{max-width:784px}.VPDoc:not(.has-sidebar) .container[data-v-a3c25e27]{max-width:1104px}}.container[data-v-a3c25e27]{margin:0 auto;width:100%}.aside[data-v-a3c25e27]{position:relative;display:none;order:2;flex-grow:1;padding-left:32px;width:100%;max-width:256px}.left-aside[data-v-a3c25e27]{order:1;padding-left:unset;padding-right:32px}.aside-container[data-v-a3c25e27]{position:fixed;top:0;padding-top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-doc-top-height, 0px) + 32px);width:224px;height:100vh;overflow-x:hidden;overflow-y:auto;scrollbar-width:none}.aside-container[data-v-a3c25e27]::-webkit-scrollbar{display:none}.aside-curtain[data-v-a3c25e27]{position:fixed;bottom:0;z-index:10;width:224px;height:32px;background:linear-gradient(transparent,var(--vp-c-bg) 70%)}.aside-content[data-v-a3c25e27]{display:flex;flex-direction:column;min-height:calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 32px));padding-bottom:32px}.content[data-v-a3c25e27]{position:relative;margin:0 auto;width:100%}@media (min-width: 960px){.content[data-v-a3c25e27]{padding:0 32px 128px}}@media (min-width: 1280px){.content[data-v-a3c25e27]{order:1;margin:0;min-width:640px}}.content-container[data-v-a3c25e27]{margin:0 auto}.VPDoc.has-aside .content-container[data-v-a3c25e27]{max-width:688px}.external-link-icon-enabled :is(.vp-doc a[href*="://"][data-v-a3c25e27],.vp-doc a[target=_blank][data-v-a3c25e27]):after{content:"";color:currentColor}.VPButton[data-v-1e76fe75]{display:inline-block;border:1px solid transparent;text-align:center;font-weight:600;white-space:nowrap;transition:color .25s,border-color .25s,background-color .25s}.VPButton[data-v-1e76fe75]:active{transition:color .1s,border-color .1s,background-color .1s}.VPButton.medium[data-v-1e76fe75]{border-radius:20px;padding:0 20px;line-height:38px;font-size:14px}.VPButton.big[data-v-1e76fe75]{border-radius:24px;padding:0 24px;line-height:46px;font-size:16px}.VPButton.brand[data-v-1e76fe75]{border-color:var(--vp-button-brand-border);color:var(--vp-button-brand-text);background-color:var(--vp-button-brand-bg)}.VPButton.brand[data-v-1e76fe75]:hover{border-color:var(--vp-button-brand-hover-border);color:var(--vp-button-brand-hover-text);background-color:var(--vp-button-brand-hover-bg)}.VPButton.brand[data-v-1e76fe75]:active{border-color:var(--vp-button-brand-active-border);color:var(--vp-button-brand-active-text);background-color:var(--vp-button-brand-active-bg)}.VPButton.alt[data-v-1e76fe75]{border-color:var(--vp-button-alt-border);color:var(--vp-button-alt-text);background-color:var(--vp-button-alt-bg)}.VPButton.alt[data-v-1e76fe75]:hover{border-color:var(--vp-button-alt-hover-border);color:var(--vp-button-alt-hover-text);background-color:var(--vp-button-alt-hover-bg)}.VPButton.alt[data-v-1e76fe75]:active{border-color:var(--vp-button-alt-active-border);color:var(--vp-button-alt-active-text);background-color:var(--vp-button-alt-active-bg)}.VPButton.sponsor[data-v-1e76fe75]{border-color:var(--vp-button-sponsor-border);color:var(--vp-button-sponsor-text);background-color:var(--vp-button-sponsor-bg)}.VPButton.sponsor[data-v-1e76fe75]:hover{border-color:var(--vp-button-sponsor-hover-border);color:var(--vp-button-sponsor-hover-text);background-color:var(--vp-button-sponsor-hover-bg)}.VPButton.sponsor[data-v-1e76fe75]:active{border-color:var(--vp-button-sponsor-active-border);color:var(--vp-button-sponsor-active-text);background-color:var(--vp-button-sponsor-active-bg)}html:not(.dark) .VPImage.dark[data-v-ab19afbb]{display:none}.dark .VPImage.light[data-v-ab19afbb]{display:none}.VPHero[data-v-5a3e9999]{margin-top:calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1);padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px}@media (min-width: 640px){.VPHero[data-v-5a3e9999]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px}}@media (min-width: 960px){.VPHero[data-v-5a3e9999]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px}}.container[data-v-5a3e9999]{display:flex;flex-direction:column;margin:0 auto;max-width:1152px}@media (min-width: 960px){.container[data-v-5a3e9999]{flex-direction:row}}.main[data-v-5a3e9999]{position:relative;z-index:10;order:2;flex-grow:1;flex-shrink:0}.VPHero.has-image .container[data-v-5a3e9999]{text-align:center}@media (min-width: 960px){.VPHero.has-image .container[data-v-5a3e9999]{text-align:left}}@media (min-width: 960px){.main[data-v-5a3e9999]{order:1;width:calc((100% / 3) * 2)}.VPHero.has-image .main[data-v-5a3e9999]{max-width:592px}}.name[data-v-5a3e9999],.text[data-v-5a3e9999]{max-width:392px;letter-spacing:-.4px;line-height:40px;font-size:32px;font-weight:700;white-space:pre-wrap}.VPHero.has-image .name[data-v-5a3e9999],.VPHero.has-image .text[data-v-5a3e9999]{margin:0 auto}.name[data-v-5a3e9999]{color:var(--vp-home-hero-name-color)}.clip[data-v-5a3e9999]{background:var(--vp-home-hero-name-background);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:var(--vp-home-hero-name-color)}@media (min-width: 640px){.name[data-v-5a3e9999],.text[data-v-5a3e9999]{max-width:576px;line-height:56px;font-size:48px}}@media (min-width: 960px){.name[data-v-5a3e9999],.text[data-v-5a3e9999]{line-height:64px;font-size:56px}.VPHero.has-image .name[data-v-5a3e9999],.VPHero.has-image .text[data-v-5a3e9999]{margin:0}}.tagline[data-v-5a3e9999]{padding-top:8px;max-width:392px;line-height:28px;font-size:18px;font-weight:500;white-space:pre-wrap;color:var(--vp-c-text-2)}.VPHero.has-image .tagline[data-v-5a3e9999]{margin:0 auto}@media (min-width: 640px){.tagline[data-v-5a3e9999]{padding-top:12px;max-width:576px;line-height:32px;font-size:20px}}@media (min-width: 960px){.tagline[data-v-5a3e9999]{line-height:36px;font-size:24px}.VPHero.has-image .tagline[data-v-5a3e9999]{margin:0}}.actions[data-v-5a3e9999]{display:flex;flex-wrap:wrap;margin:-6px;padding-top:24px}.VPHero.has-image .actions[data-v-5a3e9999]{justify-content:center}@media (min-width: 640px){.actions[data-v-5a3e9999]{padding-top:32px}}@media (min-width: 960px){.VPHero.has-image .actions[data-v-5a3e9999]{justify-content:flex-start}}.action[data-v-5a3e9999]{flex-shrink:0;padding:6px}.image[data-v-5a3e9999]{order:1;margin:-76px -24px -48px}@media (min-width: 640px){.image[data-v-5a3e9999]{margin:-108px -24px -48px}}@media (min-width: 960px){.image[data-v-5a3e9999]{flex-grow:1;order:2;margin:0;min-height:100%}}.image-container[data-v-5a3e9999]{position:relative;margin:0 auto;width:320px;height:320px}@media (min-width: 640px){.image-container[data-v-5a3e9999]{width:392px;height:392px}}@media (min-width: 960px){.image-container[data-v-5a3e9999]{display:flex;justify-content:center;align-items:center;width:100%;height:100%;transform:translate(-32px,-32px)}}.image-bg[data-v-5a3e9999]{position:absolute;top:50%;left:50%;border-radius:50%;width:192px;height:192px;background-image:var(--vp-home-hero-image-background-image);filter:var(--vp-home-hero-image-filter);transform:translate(-50%,-50%)}@media (min-width: 640px){.image-bg[data-v-5a3e9999]{width:256px;height:256px}}@media (min-width: 960px){.image-bg[data-v-5a3e9999]{width:320px;height:320px}}[data-v-5a3e9999] .image-src{position:absolute;top:50%;left:50%;max-width:192px;max-height:192px;transform:translate(-50%,-50%)}@media (min-width: 640px){[data-v-5a3e9999] .image-src{max-width:256px;max-height:256px}}@media (min-width: 960px){[data-v-5a3e9999] .image-src{max-width:320px;max-height:320px}}.VPFeature[data-v-ee984185]{display:block;border:1px solid var(--vp-c-bg-soft);border-radius:12px;height:100%;background-color:var(--vp-c-bg-soft);transition:border-color .25s,background-color .25s}.VPFeature.link[data-v-ee984185]:hover{border-color:var(--vp-c-brand-1)}.box[data-v-ee984185]{display:flex;flex-direction:column;padding:24px;height:100%}.box[data-v-ee984185]>.VPImage{margin-bottom:20px}.icon[data-v-ee984185]{display:flex;justify-content:center;align-items:center;margin-bottom:20px;border-radius:6px;background-color:var(--vp-c-default-soft);width:48px;height:48px;font-size:24px;transition:background-color .25s}.title[data-v-ee984185]{line-height:24px;font-size:16px;font-weight:600}.details[data-v-ee984185]{flex-grow:1;padding-top:8px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.link-text[data-v-ee984185]{padding-top:8px}.link-text-value[data-v-ee984185]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.link-text-icon[data-v-ee984185]{display:inline-block;margin-left:6px;width:14px;height:14px;fill:currentColor}.VPFeatures[data-v-b1eea84a]{position:relative;padding:0 24px}@media (min-width: 640px){.VPFeatures[data-v-b1eea84a]{padding:0 48px}}@media (min-width: 960px){.VPFeatures[data-v-b1eea84a]{padding:0 64px}}.container[data-v-b1eea84a]{margin:0 auto;max-width:1152px}.items[data-v-b1eea84a]{display:flex;flex-wrap:wrap;margin:-8px}.item[data-v-b1eea84a]{padding:8px;width:100%}@media (min-width: 640px){.item.grid-2[data-v-b1eea84a],.item.grid-4[data-v-b1eea84a],.item.grid-6[data-v-b1eea84a]{width:50%}}@media (min-width: 768px){.item.grid-2[data-v-b1eea84a],.item.grid-4[data-v-b1eea84a]{width:50%}.item.grid-3[data-v-b1eea84a],.item.grid-6[data-v-b1eea84a]{width:calc(100% / 3)}}@media (min-width: 960px){.item.grid-4[data-v-b1eea84a]{width:25%}}.VPHome[data-v-20eabd3a]{padding-bottom:96px}.VPHome[data-v-20eabd3a] .VPHomeSponsors{margin-top:112px;margin-bottom:-128px}@media (min-width: 768px){.VPHome[data-v-20eabd3a]{padding-bottom:128px}}.VPContent[data-v-3cf691b6]{flex-grow:1;flex-shrink:0;margin:var(--vp-layout-top-height, 0px) auto 0;width:100%}.VPContent.is-home[data-v-3cf691b6]{width:100%;max-width:100%}.VPContent.has-sidebar[data-v-3cf691b6]{margin:0}@media (min-width: 960px){.VPContent[data-v-3cf691b6]{padding-top:var(--vp-nav-height)}.VPContent.has-sidebar[data-v-3cf691b6]{margin:var(--vp-layout-top-height, 0px) 0 0;padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPContent.has-sidebar[data-v-3cf691b6]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.VPFooter[data-v-566314d4]{position:relative;z-index:var(--vp-z-index-footer);border-top:1px solid var(--vp-c-gutter);padding:32px 24px;background-color:var(--vp-c-bg)}.VPFooter.has-sidebar[data-v-566314d4]{display:none}.VPFooter[data-v-566314d4] a{text-decoration-line:underline;text-underline-offset:2px;transition:color .25s}.VPFooter[data-v-566314d4] a:hover{color:var(--vp-c-text-1)}@media (min-width: 768px){.VPFooter[data-v-566314d4]{padding:32px}}.container[data-v-566314d4]{margin:0 auto;max-width:var(--vp-layout-max-width);text-align:center}.message[data-v-566314d4],.copyright[data-v-566314d4]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.VPLocalNavOutlineDropdown[data-v-24251f6f]{padding:12px 20px 11px}.VPLocalNavOutlineDropdown button[data-v-24251f6f]{display:block;font-size:12px;font-weight:500;line-height:24px;color:var(--vp-c-text-2);transition:color .5s;position:relative}.VPLocalNavOutlineDropdown button[data-v-24251f6f]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPLocalNavOutlineDropdown button.open[data-v-24251f6f]{color:var(--vp-c-text-1)}.icon[data-v-24251f6f]{display:inline-block;vertical-align:middle;margin-left:2px;width:14px;height:14px;fill:currentColor}[data-v-24251f6f] .outline-link{font-size:14px;padding:2px 0}.open>.icon[data-v-24251f6f]{transform:rotate(90deg)}.items[data-v-24251f6f]{position:absolute;top:64px;right:16px;left:16px;display:grid;gap:1px;border:1px solid var(--vp-c-border);border-radius:8px;background-color:var(--vp-c-gutter);max-height:calc(var(--vp-vh, 100vh) - 86px);overflow:hidden auto;box-shadow:var(--vp-shadow-3)}.header[data-v-24251f6f]{background-color:var(--vp-c-bg-soft)}.top-link[data-v-24251f6f]{display:block;padding:0 16px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.outline[data-v-24251f6f]{padding:8px 0;background-color:var(--vp-c-bg-soft)}.flyout-enter-active[data-v-24251f6f]{transition:all .2s ease-out}.flyout-leave-active[data-v-24251f6f]{transition:all .15s ease-in}.flyout-enter-from[data-v-24251f6f],.flyout-leave-to[data-v-24251f6f]{opacity:0;transform:translateY(-16px)}.VPLocalNav[data-v-f8a0b38a]{position:sticky;top:0;left:0;z-index:var(--vp-z-index-local-nav);display:flex;justify-content:space-between;align-items:center;border-top:1px solid var(--vp-c-gutter);border-bottom:1px solid var(--vp-c-gutter);padding-top:var(--vp-layout-top-height, 0px);width:100%;background-color:var(--vp-local-nav-bg-color)}.VPLocalNav.fixed[data-v-f8a0b38a]{position:fixed}.VPLocalNav.reached-top[data-v-f8a0b38a]{border-top-color:transparent}@media (min-width: 960px){.VPLocalNav[data-v-f8a0b38a]{display:none}}.menu[data-v-f8a0b38a]{display:flex;align-items:center;padding:12px 24px 11px;line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.menu[data-v-f8a0b38a]:hover{color:var(--vp-c-text-1);transition:color .25s}@media (min-width: 768px){.menu[data-v-f8a0b38a]{padding:0 32px}}.menu-icon[data-v-f8a0b38a]{margin-right:8px;width:16px;height:16px;fill:currentColor}.VPOutlineDropdown[data-v-f8a0b38a]{padding:12px 24px 11px}@media (min-width: 768px){.VPOutlineDropdown[data-v-f8a0b38a]{padding:12px 32px 11px}}.VPSwitch[data-v-1c29e291]{position:relative;border-radius:11px;display:block;width:40px;height:22px;flex-shrink:0;border:1px solid var(--vp-input-border-color);background-color:var(--vp-input-switch-bg-color);transition:border-color .25s!important}.VPSwitch[data-v-1c29e291]:hover{border-color:var(--vp-c-brand-1)}.check[data-v-1c29e291]{position:absolute;top:1px;left:1px;width:18px;height:18px;border-radius:50%;background-color:var(--vp-c-neutral-inverse);box-shadow:var(--vp-shadow-1);transition:transform .25s!important}.icon[data-v-1c29e291]{position:relative;display:block;width:18px;height:18px;border-radius:50%;overflow:hidden}.icon[data-v-1c29e291] svg{position:absolute;top:3px;left:3px;width:12px;height:12px;fill:var(--vp-c-text-2)}.dark .icon[data-v-1c29e291] svg{fill:var(--vp-c-text-1);transition:opacity .25s!important}.sun[data-v-70af5d02]{opacity:1}.moon[data-v-70af5d02],.dark .sun[data-v-70af5d02]{opacity:0}.dark .moon[data-v-70af5d02]{opacity:1}.dark .VPSwitchAppearance[data-v-70af5d02] .check{transform:translate(18px)}.VPNavBarAppearance[data-v-283b26e9]{display:none}@media (min-width: 1280px){.VPNavBarAppearance[data-v-283b26e9]{display:flex;align-items:center}}.VPMenuGroup+.VPMenuLink[data-v-f51f088d]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.link[data-v-f51f088d]{display:block;border-radius:6px;padding:0 12px;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);white-space:nowrap;transition:background-color .25s,color .25s}.link[data-v-f51f088d]:hover{color:var(--vp-c-brand-1);background-color:var(--vp-c-default-soft)}.link.active[data-v-f51f088d]{color:var(--vp-c-brand-1)}.VPMenuGroup[data-v-a6b0397c]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.VPMenuGroup[data-v-a6b0397c]:first-child{margin-top:0;border-top:0;padding-top:0}.VPMenuGroup+.VPMenuGroup[data-v-a6b0397c]{margin-top:12px;border-top:1px solid var(--vp-c-divider)}.title[data-v-a6b0397c]{padding:0 12px;line-height:32px;font-size:14px;font-weight:600;color:var(--vp-c-text-2);white-space:nowrap;transition:color .25s}.VPMenu[data-v-e42ed9b3]{border-radius:12px;padding:12px;min-width:128px;border:1px solid var(--vp-c-divider);background-color:var(--vp-c-bg-elv);box-shadow:var(--vp-shadow-3);transition:background-color .5s;max-height:calc(100vh - var(--vp-nav-height));overflow-y:auto}.VPMenu[data-v-e42ed9b3] .group{margin:0 -12px;padding:0 12px 12px}.VPMenu[data-v-e42ed9b3] .group+.group{border-top:1px solid var(--vp-c-divider);padding:11px 12px 12px}.VPMenu[data-v-e42ed9b3] .group:last-child{padding-bottom:0}.VPMenu[data-v-e42ed9b3] .group+.item{border-top:1px solid var(--vp-c-divider);padding:11px 16px 0}.VPMenu[data-v-e42ed9b3] .item{padding:0 16px;white-space:nowrap}.VPMenu[data-v-e42ed9b3] .label{flex-grow:1;line-height:28px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.VPMenu[data-v-e42ed9b3] .action{padding-left:24px}.VPFlyout[data-v-aa8de344]{position:relative}.VPFlyout[data-v-aa8de344]:hover{color:var(--vp-c-brand-1);transition:color .25s}.VPFlyout:hover .text[data-v-aa8de344]{color:var(--vp-c-text-2)}.VPFlyout:hover .icon[data-v-aa8de344]{fill:var(--vp-c-text-2)}.VPFlyout.active .text[data-v-aa8de344]{color:var(--vp-c-brand-1)}.VPFlyout.active:hover .text[data-v-aa8de344]{color:var(--vp-c-brand-2)}.VPFlyout:hover .menu[data-v-aa8de344],.button[aria-expanded=true]+.menu[data-v-aa8de344]{opacity:1;visibility:visible;transform:translateY(0)}.button[aria-expanded=false]+.menu[data-v-aa8de344]{opacity:0;visibility:hidden;transform:translateY(0)}.button[data-v-aa8de344]{display:flex;align-items:center;padding:0 12px;height:var(--vp-nav-height);color:var(--vp-c-text-1);transition:color .5s}.text[data-v-aa8de344]{display:flex;align-items:center;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.option-icon[data-v-aa8de344]{margin-right:0;width:16px;height:16px;fill:currentColor}.text-icon[data-v-aa8de344]{margin-left:4px;width:14px;height:14px;fill:currentColor}.icon[data-v-aa8de344]{width:20px;height:20px;fill:currentColor;transition:fill .25s}.menu[data-v-aa8de344]{position:absolute;top:calc(var(--vp-nav-height) / 2 + 20px);right:0;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s,transform .25s}.VPSocialLink[data-v-16cf740a]{display:flex;justify-content:center;align-items:center;width:36px;height:36px;color:var(--vp-c-text-2);transition:color .5s}.VPSocialLink[data-v-16cf740a]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPSocialLink[data-v-16cf740a]>svg{width:20px;height:20px;fill:currentColor}.VPSocialLinks[data-v-e71e869c]{display:flex;justify-content:center}.VPNavBarExtra[data-v-8e87c032]{display:none;margin-right:-12px}@media (min-width: 768px){.VPNavBarExtra[data-v-8e87c032]{display:block}}@media (min-width: 1280px){.VPNavBarExtra[data-v-8e87c032]{display:none}}.trans-title[data-v-8e87c032]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.item.appearance[data-v-8e87c032],.item.social-links[data-v-8e87c032]{display:flex;align-items:center;padding:0 12px}.item.appearance[data-v-8e87c032]{min-width:176px}.appearance-action[data-v-8e87c032]{margin-right:-2px}.social-links-list[data-v-8e87c032]{margin:-4px -8px}.VPNavBarHamburger[data-v-6bee1efd]{display:flex;justify-content:center;align-items:center;width:48px;height:var(--vp-nav-height)}@media (min-width: 768px){.VPNavBarHamburger[data-v-6bee1efd]{display:none}}.container[data-v-6bee1efd]{position:relative;width:16px;height:14px;overflow:hidden}.VPNavBarHamburger:hover .top[data-v-6bee1efd]{top:0;left:0;transform:translate(4px)}.VPNavBarHamburger:hover .middle[data-v-6bee1efd]{top:6px;left:0;transform:translate(0)}.VPNavBarHamburger:hover .bottom[data-v-6bee1efd]{top:12px;left:0;transform:translate(8px)}.VPNavBarHamburger.active .top[data-v-6bee1efd]{top:6px;transform:translate(0) rotate(225deg)}.VPNavBarHamburger.active .middle[data-v-6bee1efd]{top:6px;transform:translate(16px)}.VPNavBarHamburger.active .bottom[data-v-6bee1efd]{top:6px;transform:translate(0) rotate(135deg)}.VPNavBarHamburger.active:hover .top[data-v-6bee1efd],.VPNavBarHamburger.active:hover .middle[data-v-6bee1efd],.VPNavBarHamburger.active:hover .bottom[data-v-6bee1efd]{background-color:var(--vp-c-text-2);transition:top .25s,background-color .25s,transform .25s}.top[data-v-6bee1efd],.middle[data-v-6bee1efd],.bottom[data-v-6bee1efd]{position:absolute;width:16px;height:2px;background-color:var(--vp-c-text-1);transition:top .25s,background-color .5s,transform .25s}.top[data-v-6bee1efd]{top:0;left:0;transform:translate(0)}.middle[data-v-6bee1efd]{top:6px;left:0;transform:translate(8px)}.bottom[data-v-6bee1efd]{top:12px;left:0;transform:translate(4px)}.VPNavBarMenuLink[data-v-cb318fec]{display:flex;align-items:center;padding:0 12px;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.VPNavBarMenuLink.active[data-v-cb318fec],.VPNavBarMenuLink[data-v-cb318fec]:hover{color:var(--vp-c-brand-1)}.VPNavBarMenu[data-v-f732b5d0]{display:none}@media (min-width: 768px){.VPNavBarMenu[data-v-f732b5d0]{display:flex}}/*! @docsearch/css 3.5.2 | MIT License | © Algolia, Inc. and contributors | https://docsearch.algolia.com */:root{--docsearch-primary-color:#5468ff;--docsearch-text-color:#1c1e21;--docsearch-spacing:12px;--docsearch-icon-stroke-width:1.4;--docsearch-highlight-color:var(--docsearch-primary-color);--docsearch-muted-color:#969faf;--docsearch-container-background:rgba(101,108,133,.8);--docsearch-logo-color:#5468ff;--docsearch-modal-width:560px;--docsearch-modal-height:600px;--docsearch-modal-background:#f5f6f7;--docsearch-modal-shadow:inset 1px 1px 0 0 hsla(0,0%,100%,.5),0 3px 8px 0 #555a64;--docsearch-searchbox-height:56px;--docsearch-searchbox-background:#ebedf0;--docsearch-searchbox-focus-background:#fff;--docsearch-searchbox-shadow:inset 0 0 0 2px var(--docsearch-primary-color);--docsearch-hit-height:56px;--docsearch-hit-color:#444950;--docsearch-hit-active-color:#fff;--docsearch-hit-background:#fff;--docsearch-hit-shadow:0 1px 3px 0 #d4d9e1;--docsearch-key-gradient:linear-gradient(-225deg,#d5dbe4,#f8f8f8);--docsearch-key-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px rgba(30,35,90,.4);--docsearch-footer-height:44px;--docsearch-footer-background:#fff;--docsearch-footer-shadow:0 -1px 0 0 #e0e3e8,0 -3px 6px 0 rgba(69,98,155,.12)}html[data-theme=dark]{--docsearch-text-color:#f5f6f7;--docsearch-container-background:rgba(9,10,17,.8);--docsearch-modal-background:#15172a;--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309;--docsearch-searchbox-background:#090a11;--docsearch-searchbox-focus-background:#000;--docsearch-hit-color:#bec3c9;--docsearch-hit-shadow:none;--docsearch-hit-background:#090a11;--docsearch-key-gradient:linear-gradient(-26.5deg,#565872,#31355b);--docsearch-key-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 2px 2px 0 rgba(3,4,9,.3);--docsearch-footer-background:#1e2136;--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73,76,106,.5),0 -4px 8px 0 rgba(0,0,0,.2);--docsearch-logo-color:#fff;--docsearch-muted-color:#7f8497}.DocSearch-Button{align-items:center;background:var(--docsearch-searchbox-background);border:0;border-radius:40px;color:var(--docsearch-muted-color);cursor:pointer;display:flex;font-weight:500;height:36px;justify-content:space-between;margin:0 0 0 16px;padding:0 8px;-webkit-user-select:none;user-select:none}.DocSearch-Button:active,.DocSearch-Button:focus,.DocSearch-Button:hover{background:var(--docsearch-searchbox-focus-background);box-shadow:var(--docsearch-searchbox-shadow);color:var(--docsearch-text-color);outline:none}.DocSearch-Button-Container{align-items:center;display:flex}.DocSearch-Search-Icon{stroke-width:1.6}.DocSearch-Button .DocSearch-Search-Icon{color:var(--docsearch-text-color)}.DocSearch-Button-Placeholder{font-size:1rem;padding:0 12px 0 6px}.DocSearch-Button-Keys{display:flex;min-width:calc(40px + .8em)}.DocSearch-Button-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:3px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;position:relative;padding:0 0 2px;border:0;top:-1px;width:20px}@media (max-width:768px){.DocSearch-Button-Keys,.DocSearch-Button-Placeholder{display:none}}.DocSearch--active{overflow:hidden!important}.DocSearch-Container,.DocSearch-Container *{box-sizing:border-box}.DocSearch-Container{background-color:var(--docsearch-container-background);height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:200}.DocSearch-Container a{text-decoration:none}.DocSearch-Link{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;font:inherit;margin:0;padding:0}.DocSearch-Modal{background:var(--docsearch-modal-background);border-radius:6px;box-shadow:var(--docsearch-modal-shadow);flex-direction:column;margin:60px auto auto;max-width:var(--docsearch-modal-width);position:relative}.DocSearch-SearchBar{display:flex;padding:var(--docsearch-spacing) var(--docsearch-spacing) 0}.DocSearch-Form{align-items:center;background:var(--docsearch-searchbox-focus-background);border-radius:4px;box-shadow:var(--docsearch-searchbox-shadow);display:flex;height:var(--docsearch-searchbox-height);margin:0;padding:0 var(--docsearch-spacing);position:relative;width:100%}.DocSearch-Input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;color:var(--docsearch-text-color);flex:1;font:inherit;font-size:1.2em;height:100%;outline:none;padding:0 0 0 8px;width:80%}.DocSearch-Input::placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::-webkit-search-cancel-button,.DocSearch-Input::-webkit-search-decoration,.DocSearch-Input::-webkit-search-results-button,.DocSearch-Input::-webkit-search-results-decoration{display:none}.DocSearch-LoadingIndicator,.DocSearch-MagnifierLabel,.DocSearch-Reset{margin:0;padding:0}.DocSearch-MagnifierLabel,.DocSearch-Reset{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}.DocSearch-Container--Stalled .DocSearch-MagnifierLabel,.DocSearch-LoadingIndicator{display:none}.DocSearch-Container--Stalled .DocSearch-LoadingIndicator{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Reset{animation:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;right:0;stroke-width:var(--docsearch-icon-stroke-width)}}.DocSearch-Reset{animation:fade-in .1s ease-in forwards;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;padding:2px;right:0;stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Reset[hidden]{display:none}.DocSearch-Reset:hover{color:var(--docsearch-highlight-color)}.DocSearch-LoadingIndicator svg,.DocSearch-MagnifierLabel svg{height:24px;width:24px}.DocSearch-Cancel{display:none}.DocSearch-Dropdown{max-height:calc(var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height));min-height:var(--docsearch-spacing);overflow-y:auto;overflow-y:overlay;padding:0 var(--docsearch-spacing);scrollbar-color:var(--docsearch-muted-color) var(--docsearch-modal-background);scrollbar-width:thin}.DocSearch-Dropdown::-webkit-scrollbar{width:12px}.DocSearch-Dropdown::-webkit-scrollbar-track{background:transparent}.DocSearch-Dropdown::-webkit-scrollbar-thumb{background-color:var(--docsearch-muted-color);border:3px solid var(--docsearch-modal-background);border-radius:20px}.DocSearch-Dropdown ul{list-style:none;margin:0;padding:0}.DocSearch-Label{font-size:.75em;line-height:1.6em}.DocSearch-Help,.DocSearch-Label{color:var(--docsearch-muted-color)}.DocSearch-Help{font-size:.9em;margin:0;-webkit-user-select:none;user-select:none}.DocSearch-Title{font-size:1.2em}.DocSearch-Logo a{display:flex}.DocSearch-Logo svg{color:var(--docsearch-logo-color);margin-left:8px}.DocSearch-Hits:last-of-type{margin-bottom:24px}.DocSearch-Hits mark{background:none;color:var(--docsearch-highlight-color)}.DocSearch-HitsFooter{color:var(--docsearch-muted-color);display:flex;font-size:.85em;justify-content:center;margin-bottom:var(--docsearch-spacing);padding:var(--docsearch-spacing)}.DocSearch-HitsFooter a{border-bottom:1px solid;color:inherit}.DocSearch-Hit{border-radius:4px;display:flex;padding-bottom:4px;position:relative}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--deleting{transition:none}}.DocSearch-Hit--deleting{opacity:0;transition:all .25s linear}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--favoriting{transition:none}}.DocSearch-Hit--favoriting{transform:scale(0);transform-origin:top center;transition:all .25s linear;transition-delay:.25s}.DocSearch-Hit a{background:var(--docsearch-hit-background);border-radius:4px;box-shadow:var(--docsearch-hit-shadow);display:block;padding-left:var(--docsearch-spacing);width:100%}.DocSearch-Hit-source{background:var(--docsearch-modal-background);color:var(--docsearch-highlight-color);font-size:.85em;font-weight:600;line-height:32px;margin:0 -4px;padding:8px 4px 0;position:sticky;top:0;z-index:10}.DocSearch-Hit-Tree{color:var(--docsearch-muted-color);height:var(--docsearch-hit-height);opacity:.5;stroke-width:var(--docsearch-icon-stroke-width);width:24px}.DocSearch-Hit[aria-selected=true] a{background-color:var(--docsearch-highlight-color)}.DocSearch-Hit[aria-selected=true] mark{text-decoration:underline}.DocSearch-Hit-Container{align-items:center;color:var(--docsearch-hit-color);display:flex;flex-direction:row;height:var(--docsearch-hit-height);padding:0 var(--docsearch-spacing) 0 0}.DocSearch-Hit-icon{height:20px;width:20px}.DocSearch-Hit-action,.DocSearch-Hit-icon{color:var(--docsearch-muted-color);stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Hit-action{align-items:center;display:flex;height:22px;width:22px}.DocSearch-Hit-action svg{display:block;height:18px;width:18px}.DocSearch-Hit-action+.DocSearch-Hit-action{margin-left:6px}.DocSearch-Hit-action-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:inherit;cursor:pointer;padding:2px}svg.DocSearch-Hit-Select-Icon{display:none}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Select-Icon{display:block}.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:background-color .1s ease-in}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{transition:none}}.DocSearch-Hit-action-button:focus path,.DocSearch-Hit-action-button:hover path{fill:#fff}.DocSearch-Hit-content-wrapper{display:flex;flex:1 1 auto;flex-direction:column;font-weight:500;justify-content:center;line-height:1.2em;margin:0 8px;overflow-x:hidden;position:relative;text-overflow:ellipsis;white-space:nowrap;width:80%}.DocSearch-Hit-title{font-size:.9em}.DocSearch-Hit-path{color:var(--docsearch-muted-color);font-size:.75em}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-action,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-icon,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-text,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-title,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Tree,.DocSearch-Hit[aria-selected=true] mark{color:var(--docsearch-hit-active-color)!important}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:none}}.DocSearch-ErrorScreen,.DocSearch-NoResults,.DocSearch-StartScreen{font-size:.9em;margin:0 auto;padding:36px 0;text-align:center;width:80%}.DocSearch-Screen-Icon{color:var(--docsearch-muted-color);padding-bottom:12px}.DocSearch-NoResults-Prefill-List{display:inline-block;padding-bottom:24px;text-align:left}.DocSearch-NoResults-Prefill-List ul{display:inline-block;padding:8px 0 0}.DocSearch-NoResults-Prefill-List li{list-style-position:inside;list-style-type:"» "}.DocSearch-Prefill{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:1em;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;font-size:1em;font-weight:700;padding:0}.DocSearch-Prefill:focus,.DocSearch-Prefill:hover{outline:none;text-decoration:underline}.DocSearch-Footer{align-items:center;background:var(--docsearch-footer-background);border-radius:0 0 8px 8px;box-shadow:var(--docsearch-footer-shadow);display:flex;flex-direction:row-reverse;flex-shrink:0;height:var(--docsearch-footer-height);justify-content:space-between;padding:0 var(--docsearch-spacing);position:relative;-webkit-user-select:none;user-select:none;width:100%;z-index:300}.DocSearch-Commands{color:var(--docsearch-muted-color);display:flex;list-style:none;margin:0;padding:0}.DocSearch-Commands li{align-items:center;display:flex}.DocSearch-Commands li:not(:last-of-type){margin-right:.8em}.DocSearch-Commands-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:2px;box-shadow:var(--docsearch-key-shadow);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 1px;color:var(--docsearch-muted-color);border:0;width:20px}@media (max-width:768px){:root{--docsearch-spacing:10px;--docsearch-footer-height:40px}.DocSearch-Dropdown{height:100%}.DocSearch-Container{height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);position:absolute}.DocSearch-Footer{border-radius:0;bottom:0;position:absolute}.DocSearch-Hit-content-wrapper{display:flex;position:relative;width:80%}.DocSearch-Modal{border-radius:0;box-shadow:none;height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);margin:0;max-width:100%;width:100%}.DocSearch-Dropdown{max-height:calc(var(--docsearch-vh, 1vh)*100 - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height))}.DocSearch-Cancel{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;flex:none;font:inherit;font-size:1em;font-weight:500;margin-left:var(--docsearch-spacing);outline:none;overflow:hidden;padding:0;-webkit-user-select:none;user-select:none;white-space:nowrap}.DocSearch-Commands,.DocSearch-Hit-Tree{display:none}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}[class*=DocSearch]{--docsearch-primary-color: var(--vp-c-brand-1);--docsearch-highlight-color: var(--docsearch-primary-color);--docsearch-text-color: var(--vp-c-text-1);--docsearch-muted-color: var(--vp-c-text-2);--docsearch-searchbox-shadow: none;--docsearch-searchbox-background: transparent;--docsearch-searchbox-focus-background: transparent;--docsearch-key-gradient: transparent;--docsearch-key-shadow: none;--docsearch-modal-background: var(--vp-c-bg-soft);--docsearch-footer-background: var(--vp-c-bg)}.dark [class*=DocSearch]{--docsearch-modal-shadow: none;--docsearch-footer-shadow: none;--docsearch-logo-color: var(--vp-c-text-2);--docsearch-hit-background: var(--vp-c-default-soft);--docsearch-hit-color: var(--vp-c-text-2);--docsearch-hit-shadow: none}.DocSearch-Button{display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:48px;height:55px;background:transparent;transition:border-color .25s}.DocSearch-Button:hover{background:transparent}.DocSearch-Button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.DocSearch-Button:focus:not(:focus-visible){outline:none!important}@media (min-width: 768px){.DocSearch-Button{justify-content:flex-start;border:1px solid transparent;border-radius:8px;padding:0 10px 0 12px;width:100%;height:40px;background-color:var(--vp-c-bg-alt)}.DocSearch-Button:hover{border-color:var(--vp-c-brand-1);background:var(--vp-c-bg-alt)}}.DocSearch-Button .DocSearch-Button-Container{display:flex;align-items:center}.DocSearch-Button .DocSearch-Search-Icon{position:relative;width:16px;height:16px;color:var(--vp-c-text-1);fill:currentColor;transition:color .5s}.DocSearch-Button:hover .DocSearch-Search-Icon{color:var(--vp-c-text-1)}@media (min-width: 768px){.DocSearch-Button .DocSearch-Search-Icon{top:1px;margin-right:8px;width:14px;height:14px;color:var(--vp-c-text-2)}}.DocSearch-Button .DocSearch-Button-Placeholder{display:none;margin-top:2px;padding:0 16px 0 0;font-size:13px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.DocSearch-Button:hover .DocSearch-Button-Placeholder{color:var(--vp-c-text-1)}@media (min-width: 768px){.DocSearch-Button .DocSearch-Button-Placeholder{display:inline-block}}.DocSearch-Button .DocSearch-Button-Keys{direction:ltr;display:none;min-width:auto}@media (min-width: 768px){.DocSearch-Button .DocSearch-Button-Keys{display:flex;align-items:center}}.DocSearch-Button .DocSearch-Button-Key{display:block;margin:2px 0 0;border:1px solid var(--vp-c-divider);border-right:none;border-radius:4px 0 0 4px;padding-left:6px;min-width:0;width:auto;height:22px;line-height:22px;font-family:var(--vp-font-family-base);font-size:12px;font-weight:500;transition:color .5s,border-color .5s}.DocSearch-Button .DocSearch-Button-Key+.DocSearch-Button-Key{border-right:1px solid var(--vp-c-divider);border-left:none;border-radius:0 4px 4px 0;padding-left:2px;padding-right:6px}.DocSearch-Button .DocSearch-Button-Key:first-child{font-size:0!important}.DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"Ctrl";font-size:12px;letter-spacing:normal;color:var(--docsearch-muted-color)}.mac .DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"⌘"}.DocSearch-Button .DocSearch-Button-Key:first-child>*{display:none}.VPNavBarSearch{display:flex;align-items:center}@media (min-width: 768px){.VPNavBarSearch{flex-grow:1;padding-left:24px}}@media (min-width: 960px){.VPNavBarSearch{padding-left:32px}}.dark .DocSearch-Footer{border-top:1px solid var(--vp-c-divider)}.DocSearch-Form{border:1px solid var(--vp-c-brand-1);background-color:var(--vp-c-white)}.dark .DocSearch-Form{background-color:var(--vp-c-default-soft)}.DocSearch-Screen-Icon>svg{margin:auto}.VPNavBarSocialLinks[data-v-ef6192dc]{display:none}@media (min-width: 1280px){.VPNavBarSocialLinks[data-v-ef6192dc]{display:flex;align-items:center}}.title[data-v-2973dbb4]{display:flex;align-items:center;border-bottom:1px solid transparent;width:100%;height:var(--vp-nav-height);font-size:16px;font-weight:600;color:var(--vp-c-text-1);transition:opacity .25s}@media (min-width: 960px){.title[data-v-2973dbb4]{flex-shrink:0}.VPNavBarTitle.has-sidebar .title[data-v-2973dbb4]{border-bottom-color:var(--vp-c-divider)}}[data-v-2973dbb4] .logo{margin-right:8px;height:var(--vp-nav-logo-height)}.VPNavBarTranslations[data-v-ff4524ae]{display:none}@media (min-width: 1280px){.VPNavBarTranslations[data-v-ff4524ae]{display:flex;align-items:center}}.title[data-v-ff4524ae]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.VPNavBar[data-v-5befd255]{position:relative;border-bottom:1px solid transparent;padding:0 8px 0 24px;height:var(--vp-nav-height);pointer-events:none;white-space:nowrap}@media (min-width: 768px){.VPNavBar[data-v-5befd255]{padding:0 32px}}@media (min-width: 960px){.VPNavBar.has-sidebar[data-v-5befd255]{padding:0}.VPNavBar[data-v-5befd255]:not(.has-sidebar):not(.top){border-bottom-color:var(--vp-c-gutter);background-color:var(--vp-nav-bg-color)}}.container[data-v-5befd255]{display:flex;justify-content:space-between;margin:0 auto;max-width:calc(var(--vp-layout-max-width) - 64px);height:var(--vp-nav-height);pointer-events:none}.container>.title[data-v-5befd255],.container>.content[data-v-5befd255]{pointer-events:none}.container[data-v-5befd255] *{pointer-events:auto}@media (min-width: 960px){.VPNavBar.has-sidebar .container[data-v-5befd255]{max-width:100%}}.title[data-v-5befd255]{flex-shrink:0;height:calc(var(--vp-nav-height) - 1px);transition:background-color .5s}@media (min-width: 960px){.VPNavBar.has-sidebar .title[data-v-5befd255]{position:absolute;top:0;left:0;z-index:2;padding:0 32px;width:var(--vp-sidebar-width);height:var(--vp-nav-height);background-color:transparent}}@media (min-width: 1440px){.VPNavBar.has-sidebar .title[data-v-5befd255]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}.content[data-v-5befd255]{flex-grow:1}@media (min-width: 960px){.VPNavBar.has-sidebar .content[data-v-5befd255]{position:relative;z-index:1;padding-right:32px;padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPNavBar.has-sidebar .content[data-v-5befd255]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.content-body[data-v-5befd255]{display:flex;justify-content:flex-end;align-items:center;height:calc(var(--vp-nav-height) - 1px);transition:background-color .5s}@media (min-width: 960px){.VPNavBar:not(.top) .content-body[data-v-5befd255]{position:relative;background-color:var(--vp-nav-bg-color)}}@media (max-width: 767px){.content-body[data-v-5befd255]{column-gap:.5rem}}.menu+.translations[data-v-5befd255]:before,.menu+.appearance[data-v-5befd255]:before,.menu+.social-links[data-v-5befd255]:before,.translations+.appearance[data-v-5befd255]:before,.appearance+.social-links[data-v-5befd255]:before{margin-right:8px;margin-left:8px;width:1px;height:24px;background-color:var(--vp-c-divider);content:""}.menu+.appearance[data-v-5befd255]:before,.translations+.appearance[data-v-5befd255]:before{margin-right:16px}.appearance+.social-links[data-v-5befd255]:before{margin-left:16px}.social-links[data-v-5befd255]{margin-right:-8px}@media (min-width: 960px){.VPNavBar.has-sidebar .curtain[data-v-5befd255]{position:absolute;right:0;bottom:-31px;width:calc(100% - var(--vp-sidebar-width));height:32px}.VPNavBar.has-sidebar .curtain[data-v-5befd255]:before{display:block;width:100%;height:32px;background:linear-gradient(var(--vp-c-bg),transparent 70%);content:""}}@media (min-width: 1440px){.VPNavBar.has-sidebar .curtain[data-v-5befd255]{width:calc(100% - ((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width)))}}.VPNavScreenAppearance[data-v-338d9b48]{display:flex;justify-content:space-between;align-items:center;border-radius:8px;padding:12px 14px 12px 16px;background-color:var(--vp-c-bg-soft)}.text[data-v-338d9b48]{line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.VPNavScreenMenuLink[data-v-fe523e3d]{display:block;border-bottom:1px solid var(--vp-c-divider);padding:12px 0 11px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:border-color .25s,color .25s}.VPNavScreenMenuLink[data-v-fe523e3d]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupLink[data-v-aea78dd1]{display:block;margin-left:12px;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-1);transition:color .25s}.VPNavScreenMenuGroupLink[data-v-aea78dd1]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupSection[data-v-f60dbfa7]{display:block}.title[data-v-f60dbfa7]{line-height:32px;font-size:13px;font-weight:700;color:var(--vp-c-text-2);transition:color .25s}.VPNavScreenMenuGroup[data-v-32e4a89c]{border-bottom:1px solid var(--vp-c-divider);height:48px;overflow:hidden;transition:border-color .5s}.VPNavScreenMenuGroup .items[data-v-32e4a89c]{visibility:hidden}.VPNavScreenMenuGroup.open .items[data-v-32e4a89c]{visibility:visible}.VPNavScreenMenuGroup.open[data-v-32e4a89c]{padding-bottom:10px;height:auto}.VPNavScreenMenuGroup.open .button[data-v-32e4a89c]{padding-bottom:6px;color:var(--vp-c-brand-1)}.VPNavScreenMenuGroup.open .button-icon[data-v-32e4a89c]{transform:rotate(45deg)}.button[data-v-32e4a89c]{display:flex;justify-content:space-between;align-items:center;padding:12px 4px 11px 0;width:100%;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.button[data-v-32e4a89c]:hover{color:var(--vp-c-brand-1)}.button-icon[data-v-32e4a89c]{width:14px;height:14px;fill:var(--vp-c-text-2);transition:fill .5s,transform .25s}.group[data-v-32e4a89c]:first-child{padding-top:0}.group+.group[data-v-32e4a89c],.group+.item[data-v-32e4a89c]{padding-top:4px}.VPNavScreenTranslations[data-v-41505286]{height:24px;overflow:hidden}.VPNavScreenTranslations.open[data-v-41505286]{height:auto}.title[data-v-41505286]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-text-1)}.icon[data-v-41505286]{width:16px;height:16px;fill:currentColor}.icon.lang[data-v-41505286]{margin-right:8px}.icon.chevron[data-v-41505286]{margin-left:4px}.list[data-v-41505286]{padding:4px 0 0 24px}.link[data-v-41505286]{line-height:32px;font-size:13px;color:var(--vp-c-text-1)}.VPNavScreen[data-v-57cce842]{position:fixed;top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 1px);right:0;bottom:0;left:0;padding:0 32px;width:100%;background-color:var(--vp-nav-screen-bg-color);overflow-y:auto;transition:background-color .5s;pointer-events:auto}.VPNavScreen.fade-enter-active[data-v-57cce842],.VPNavScreen.fade-leave-active[data-v-57cce842]{transition:opacity .25s}.VPNavScreen.fade-enter-active .container[data-v-57cce842],.VPNavScreen.fade-leave-active .container[data-v-57cce842]{transition:transform .25s ease}.VPNavScreen.fade-enter-from[data-v-57cce842],.VPNavScreen.fade-leave-to[data-v-57cce842]{opacity:0}.VPNavScreen.fade-enter-from .container[data-v-57cce842],.VPNavScreen.fade-leave-to .container[data-v-57cce842]{transform:translateY(-8px)}@media (min-width: 768px){.VPNavScreen[data-v-57cce842]{display:none}}.container[data-v-57cce842]{margin:0 auto;padding:24px 0 96px;max-width:288px}.menu+.translations[data-v-57cce842],.menu+.appearance[data-v-57cce842],.translations+.appearance[data-v-57cce842]{margin-top:24px}.menu+.social-links[data-v-57cce842]{margin-top:16px}.appearance+.social-links[data-v-57cce842]{margin-top:16px}.VPNav[data-v-7ad780c2]{position:relative;top:var(--vp-layout-top-height, 0px);left:0;z-index:var(--vp-z-index-nav);width:100%;pointer-events:none;transition:background-color .5s}@media (min-width: 960px){.VPNav[data-v-7ad780c2]{position:fixed}}.VPSidebarItem.level-0[data-v-bd01e0d5]{padding-bottom:24px}.VPSidebarItem.collapsed.level-0[data-v-bd01e0d5]{padding-bottom:10px}.item[data-v-bd01e0d5]{position:relative;display:flex;width:100%}.VPSidebarItem.collapsible>.item[data-v-bd01e0d5]{cursor:pointer}.indicator[data-v-bd01e0d5]{position:absolute;top:6px;bottom:6px;left:-17px;width:2px;border-radius:2px;transition:background-color .25s}.VPSidebarItem.level-2.is-active>.item>.indicator[data-v-bd01e0d5],.VPSidebarItem.level-3.is-active>.item>.indicator[data-v-bd01e0d5],.VPSidebarItem.level-4.is-active>.item>.indicator[data-v-bd01e0d5],.VPSidebarItem.level-5.is-active>.item>.indicator[data-v-bd01e0d5]{background-color:var(--vp-c-brand-1)}.link[data-v-bd01e0d5]{display:flex;align-items:center;flex-grow:1}.text[data-v-bd01e0d5]{flex-grow:1;padding:4px 0;line-height:24px;font-size:14px;transition:color .25s}.VPSidebarItem.level-0 .text[data-v-bd01e0d5]{font-weight:700;color:var(--vp-c-text-1)}.VPSidebarItem.level-1 .text[data-v-bd01e0d5],.VPSidebarItem.level-2 .text[data-v-bd01e0d5],.VPSidebarItem.level-3 .text[data-v-bd01e0d5],.VPSidebarItem.level-4 .text[data-v-bd01e0d5],.VPSidebarItem.level-5 .text[data-v-bd01e0d5]{font-weight:500;color:var(--vp-c-text-2)}.VPSidebarItem.level-0.is-link>.item>.link:hover .text[data-v-bd01e0d5],.VPSidebarItem.level-1.is-link>.item>.link:hover .text[data-v-bd01e0d5],.VPSidebarItem.level-2.is-link>.item>.link:hover .text[data-v-bd01e0d5],.VPSidebarItem.level-3.is-link>.item>.link:hover .text[data-v-bd01e0d5],.VPSidebarItem.level-4.is-link>.item>.link:hover .text[data-v-bd01e0d5],.VPSidebarItem.level-5.is-link>.item>.link:hover .text[data-v-bd01e0d5]{color:var(--vp-c-brand-1)}.VPSidebarItem.level-0.has-active>.item>.text[data-v-bd01e0d5],.VPSidebarItem.level-1.has-active>.item>.text[data-v-bd01e0d5],.VPSidebarItem.level-2.has-active>.item>.text[data-v-bd01e0d5],.VPSidebarItem.level-3.has-active>.item>.text[data-v-bd01e0d5],.VPSidebarItem.level-4.has-active>.item>.text[data-v-bd01e0d5],.VPSidebarItem.level-5.has-active>.item>.text[data-v-bd01e0d5],.VPSidebarItem.level-0.has-active>.item>.link>.text[data-v-bd01e0d5],.VPSidebarItem.level-1.has-active>.item>.link>.text[data-v-bd01e0d5],.VPSidebarItem.level-2.has-active>.item>.link>.text[data-v-bd01e0d5],.VPSidebarItem.level-3.has-active>.item>.link>.text[data-v-bd01e0d5],.VPSidebarItem.level-4.has-active>.item>.link>.text[data-v-bd01e0d5],.VPSidebarItem.level-5.has-active>.item>.link>.text[data-v-bd01e0d5]{color:var(--vp-c-text-1)}.VPSidebarItem.level-0.is-active>.item .link>.text[data-v-bd01e0d5],.VPSidebarItem.level-1.is-active>.item .link>.text[data-v-bd01e0d5],.VPSidebarItem.level-2.is-active>.item .link>.text[data-v-bd01e0d5],.VPSidebarItem.level-3.is-active>.item .link>.text[data-v-bd01e0d5],.VPSidebarItem.level-4.is-active>.item .link>.text[data-v-bd01e0d5],.VPSidebarItem.level-5.is-active>.item .link>.text[data-v-bd01e0d5]{color:var(--vp-c-brand-1)}.caret[data-v-bd01e0d5]{display:flex;justify-content:center;align-items:center;margin-right:-7px;width:32px;height:32px;color:var(--vp-c-text-3);cursor:pointer;transition:color .25s;flex-shrink:0}.item:hover .caret[data-v-bd01e0d5]{color:var(--vp-c-text-2)}.item:hover .caret[data-v-bd01e0d5]:hover{color:var(--vp-c-text-1)}.caret-icon[data-v-bd01e0d5]{width:18px;height:18px;fill:currentColor;transform:rotate(90deg);transition:transform .25s}.VPSidebarItem.collapsed .caret-icon[data-v-bd01e0d5]{transform:rotate(0)}.VPSidebarItem.level-1 .items[data-v-bd01e0d5],.VPSidebarItem.level-2 .items[data-v-bd01e0d5],.VPSidebarItem.level-3 .items[data-v-bd01e0d5],.VPSidebarItem.level-4 .items[data-v-bd01e0d5],.VPSidebarItem.level-5 .items[data-v-bd01e0d5]{border-left:1px solid var(--vp-c-divider);padding-left:16px}.VPSidebarItem.collapsed .items[data-v-bd01e0d5]{display:none}.VPSidebar[data-v-168699b1]{position:fixed;top:var(--vp-layout-top-height, 0px);bottom:0;left:0;z-index:var(--vp-z-index-sidebar);padding:32px 32px 96px;width:calc(100vw - 64px);max-width:320px;background-color:var(--vp-sidebar-bg-color);opacity:0;box-shadow:var(--vp-c-shadow-3);overflow-x:hidden;overflow-y:auto;transform:translate(-100%);transition:opacity .5s,transform .25s ease;overscroll-behavior:contain}.VPSidebar.open[data-v-168699b1]{opacity:1;visibility:visible;transform:translate(0);transition:opacity .25s,transform .5s cubic-bezier(.19,1,.22,1)}.dark .VPSidebar[data-v-168699b1]{box-shadow:var(--vp-shadow-1)}@media (min-width: 960px){.VPSidebar[data-v-168699b1]{z-index:1;padding-top:var(--vp-nav-height);width:var(--vp-sidebar-width);max-width:100%;background-color:var(--vp-sidebar-bg-color);opacity:1;visibility:visible;box-shadow:none;transform:translate(0)}}@media (min-width: 1440px){.VPSidebar[data-v-168699b1]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}@media (min-width: 960px){.curtain[data-v-168699b1]{position:sticky;top:-64px;left:0;z-index:1;margin-top:calc(var(--vp-nav-height) * -1);margin-right:-32px;margin-left:-32px;height:var(--vp-nav-height);background-color:var(--vp-sidebar-bg-color)}}.nav[data-v-168699b1]{outline:0}.group+.group[data-v-168699b1]{border-top:1px solid var(--vp-c-divider);padding-top:10px}@media (min-width: 960px){.group[data-v-168699b1]{padding-top:10px;width:calc(var(--vp-sidebar-width) - 64px)}}.VPSkipLink[data-v-c8291ffa]{top:8px;left:8px;padding:8px 16px;z-index:999;border-radius:8px;font-size:12px;font-weight:700;text-decoration:none;color:var(--vp-c-brand-1);box-shadow:var(--vp-shadow-3);background-color:var(--vp-c-bg)}.VPSkipLink[data-v-c8291ffa]:focus{height:auto;width:auto;clip:auto;clip-path:none}@media (min-width: 1280px){.VPSkipLink[data-v-c8291ffa]{top:14px;left:16px}}.Layout[data-v-9d8abc1e]{display:flex;flex-direction:column;min-height:100vh}.VPHomeSponsors[data-v-843cc1b2]{border-top:1px solid var(--vp-c-gutter);padding:88px 24px 96px;background-color:var(--vp-c-bg)}.container[data-v-843cc1b2]{margin:0 auto;max-width:1152px}.love[data-v-843cc1b2]{margin:0 auto;width:28px;height:28px;color:var(--vp-c-text-3)}.icon[data-v-843cc1b2]{width:28px;height:28px;fill:currentColor}.message[data-v-843cc1b2]{margin:0 auto;padding-top:10px;max-width:320px;text-align:center;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}.sponsors[data-v-843cc1b2]{padding-top:32px}.action[data-v-843cc1b2]{padding-top:40px;text-align:center}.VPTeamPage[data-v-b1cfd8dc]{padding-bottom:96px}@media (min-width: 768px){.VPTeamPage[data-v-b1cfd8dc]{padding-bottom:128px}}.VPTeamPageSection+.VPTeamPageSection[data-v-b1cfd8dc-s],.VPTeamMembers+.VPTeamPageSection[data-v-b1cfd8dc-s]{margin-top:64px}.VPTeamMembers+.VPTeamMembers[data-v-b1cfd8dc-s]{margin-top:24px}@media (min-width: 768px){.VPTeamPageTitle+.VPTeamPageSection[data-v-b1cfd8dc-s]{margin-top:16px}.VPTeamPageSection+.VPTeamPageSection[data-v-b1cfd8dc-s],.VPTeamMembers+.VPTeamPageSection[data-v-b1cfd8dc-s]{margin-top:96px}}.VPTeamMembers[data-v-b1cfd8dc-s]{padding:0 24px}@media (min-width: 768px){.VPTeamMembers[data-v-b1cfd8dc-s]{padding:0 48px}}@media (min-width: 960px){.VPTeamMembers[data-v-b1cfd8dc-s]{padding:0 64px}}.VPTeamPageTitle[data-v-46c5e327]{padding:48px 32px;text-align:center}@media (min-width: 768px){.VPTeamPageTitle[data-v-46c5e327]{padding:64px 48px 48px}}@media (min-width: 960px){.VPTeamPageTitle[data-v-46c5e327]{padding:80px 64px 48px}}.title[data-v-46c5e327]{letter-spacing:0;line-height:44px;font-size:36px;font-weight:500}@media (min-width: 768px){.title[data-v-46c5e327]{letter-spacing:-.5px;line-height:56px;font-size:48px}}.lead[data-v-46c5e327]{margin:0 auto;max-width:512px;padding-top:12px;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}@media (min-width: 768px){.lead[data-v-46c5e327]{max-width:592px;letter-spacing:.15px;line-height:28px;font-size:20px}}.VPTeamPageSection[data-v-3bf2e850]{padding:0 32px}@media (min-width: 768px){.VPTeamPageSection[data-v-3bf2e850]{padding:0 48px}}@media (min-width: 960px){.VPTeamPageSection[data-v-3bf2e850]{padding:0 64px}}.title[data-v-3bf2e850]{position:relative;margin:0 auto;max-width:1152px;text-align:center;color:var(--vp-c-text-2)}.title-line[data-v-3bf2e850]{position:absolute;top:16px;left:0;width:100%;height:1px;background-color:var(--vp-c-divider)}.title-text[data-v-3bf2e850]{position:relative;display:inline-block;padding:0 24px;letter-spacing:0;line-height:32px;font-size:20px;font-weight:500;background-color:var(--vp-c-bg)}.lead[data-v-3bf2e850]{margin:0 auto;max-width:480px;padding-top:12px;text-align:center;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}.members[data-v-3bf2e850]{padding-top:40px}.VPTeamMembersItem[data-v-3a0078bd]{display:flex;flex-direction:column;gap:2px;border-radius:12px;width:100%;height:100%;overflow:hidden}.VPTeamMembersItem.small .profile[data-v-3a0078bd]{padding:32px}.VPTeamMembersItem.small .data[data-v-3a0078bd]{padding-top:20px}.VPTeamMembersItem.small .avatar[data-v-3a0078bd]{width:64px;height:64px}.VPTeamMembersItem.small .name[data-v-3a0078bd]{line-height:24px;font-size:16px}.VPTeamMembersItem.small .affiliation[data-v-3a0078bd]{padding-top:4px;line-height:20px;font-size:14px}.VPTeamMembersItem.small .desc[data-v-3a0078bd]{padding-top:12px;line-height:20px;font-size:14px}.VPTeamMembersItem.small .links[data-v-3a0078bd]{margin:0 -16px -20px;padding:10px 0 0}.VPTeamMembersItem.medium .profile[data-v-3a0078bd]{padding:48px 32px}.VPTeamMembersItem.medium .data[data-v-3a0078bd]{padding-top:24px;text-align:center}.VPTeamMembersItem.medium .avatar[data-v-3a0078bd]{width:96px;height:96px}.VPTeamMembersItem.medium .name[data-v-3a0078bd]{letter-spacing:.15px;line-height:28px;font-size:20px}.VPTeamMembersItem.medium .affiliation[data-v-3a0078bd]{padding-top:4px;font-size:16px}.VPTeamMembersItem.medium .desc[data-v-3a0078bd]{padding-top:16px;max-width:288px;font-size:16px}.VPTeamMembersItem.medium .links[data-v-3a0078bd]{margin:0 -16px -12px;padding:16px 12px 0}.profile[data-v-3a0078bd]{flex-grow:1;background-color:var(--vp-c-bg-soft)}.data[data-v-3a0078bd]{text-align:center}.avatar[data-v-3a0078bd]{position:relative;flex-shrink:0;margin:0 auto;border-radius:50%;box-shadow:var(--vp-shadow-3)}.avatar-img[data-v-3a0078bd]{position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;object-fit:cover}.name[data-v-3a0078bd]{margin:0;font-weight:600}.affiliation[data-v-3a0078bd]{margin:0;font-weight:500;color:var(--vp-c-text-2)}.org.link[data-v-3a0078bd]{color:var(--vp-c-text-2);transition:color .25s}.org.link[data-v-3a0078bd]:hover{color:var(--vp-c-brand-1)}.desc[data-v-3a0078bd]{margin:0 auto}.desc[data-v-3a0078bd] a{font-weight:500;color:var(--vp-c-brand-1);text-decoration-style:dotted;transition:color .25s}.links[data-v-3a0078bd]{display:flex;justify-content:center;height:56px}.sp-link[data-v-3a0078bd]{display:flex;justify-content:center;align-items:center;text-align:center;padding:16px;font-size:14px;font-weight:500;color:var(--vp-c-sponsor);background-color:var(--vp-c-bg-soft);transition:color .25s,background-color .25s}.sp .sp-link.link[data-v-3a0078bd]:hover,.sp .sp-link.link[data-v-3a0078bd]:focus{outline:none;color:var(--vp-c-white);background-color:var(--vp-c-sponsor)}.sp-icon[data-v-3a0078bd]{margin-right:8px;width:16px;height:16px;fill:currentColor}.VPTeamMembers.small .container[data-v-bf782009]{grid-template-columns:repeat(auto-fit,minmax(224px,1fr))}.VPTeamMembers.small.count-1 .container[data-v-bf782009]{max-width:276px}.VPTeamMembers.small.count-2 .container[data-v-bf782009]{max-width:576px}.VPTeamMembers.small.count-3 .container[data-v-bf782009]{max-width:876px}.VPTeamMembers.medium .container[data-v-bf782009]{grid-template-columns:repeat(auto-fit,minmax(256px,1fr))}@media (min-width: 375px){.VPTeamMembers.medium .container[data-v-bf782009]{grid-template-columns:repeat(auto-fit,minmax(288px,1fr))}}.VPTeamMembers.medium.count-1 .container[data-v-bf782009]{max-width:368px}.VPTeamMembers.medium.count-2 .container[data-v-bf782009]{max-width:760px}.container[data-v-bf782009]{display:grid;gap:24px;margin:0 auto;max-width:1152px}.VPHero .text{font-size:42px;line-height:1.1;padding:16px 0;color:var(--logo-text-color)}.VPHero .VPImage{width:100%}.ext-logo{min-width:80px;max-width:80px;border-radius:8px}.hero-logo-horiz{font-size:calc(20px + max(35px,min(7vw,70px)))!important;white-space:nowrap;text-align:center;margin-bottom:.4em;color:var(--logo-text-color)}.hero-logo-horiz img{height:3ex;display:inline-block;vertical-align:middle;padding-bottom:.35ex;margin-right:-.3ex}:root{--vp-c-default-1: var(--vp-c-gray-1);--vp-c-default-2: var(--vp-c-gray-2);--vp-c-default-3: var(--vp-c-gray-3);--vp-c-default-soft: var(--vp-c-gray-soft);--vp-c-brand-1: rgb(39 152 212);--vp-c-brand-2: rgb(1 118 181);--vp-c-brand-3: rgb(24 65 113);--vp-c-brand-soft: var(--vp-c-indigo-soft);--vp-c-tip-1: var(--vp-c-brand-1);--vp-c-tip-2: var(--vp-c-brand-2);--vp-c-tip-3: var(--vp-c-brand-3);--vp-c-tip-soft: var(--vp-c-brand-soft);--vp-c-warning-1: var(--vp-c-yellow-1);--vp-c-warning-2: var(--vp-c-yellow-2);--vp-c-warning-3: var(--vp-c-yellow-3);--vp-c-warning-soft: var(--vp-c-yellow-soft);--vp-c-danger-1: var(--vp-c-red-1);--vp-c-danger-2: var(--vp-c-red-2);--vp-c-danger-3: var(--vp-c-red-3);--vp-c-danger-soft: var(--vp-c-red-soft);--logo-text-color: var(--vp-c-brand-3);--vp-code-block-bg: #2b2b2b}html:not(.dark) .vp-doc div[class*=language-] code{color:var(--vp-c-bg)}.dark{--vp-c-text-1: rgb(185, 185, 185);--logo-text-color: #e7e7e7;--vp-code-block-bg: var(--vp-c-bg-alt)}:root{--vp-button-brand-border: transparent;--vp-button-brand-text: var(--vp-c-white);--vp-button-brand-bg: var(--vp-c-brand-3);--vp-button-brand-hover-border: transparent;--vp-button-brand-hover-text: var(--vp-c-white);--vp-button-brand-hover-bg: var(--vp-c-brand-2);--vp-button-brand-active-border: transparent;--vp-button-brand-active-text: var(--vp-c-white);--vp-button-brand-active-bg: var(--vp-c-brand-1)}:root{--vp-home-hero-name-color: transparent;--vp-home-hero-name-background: -webkit-linear-gradient( 120deg, var(--vp-c-brand-2) 30%, var(--vp-c-brand-1) );--vp-home-hero-image-background-image: linear-gradient( -45deg, var(--vp-c-brand-2) 50%, var(--vp-c-brand-1) 50% );--vp-home-hero-image-opacity: .5;--vp-home-hero-image-spread-mult: 1.5;--vp-home-hero-image-filter: blur(calc(44px * var(--vp-home-hero-image-spread-mult))) opacity(var(--vp-home-hero-image-opacity))}.dark{--vp-home-hero-image-background-image: linear-gradient( -45deg, var(--vp-c-brand-3) 50%, var(--vp-c-brand-2) 50% );--vp-home-hero-image-spread-mult: 1}@media (min-width: 640px){:root{--vp-home-hero-image-filter: blur(calc(56px * var(--vp-home-hero-image-spread-mult))) opacity(var(--vp-home-hero-image-opacity))}}@media (min-width: 960px){:root{--vp-home-hero-image-filter: blur(calc(68px * var(--vp-home-hero-image-spread-mult))) opacity(var(--vp-home-hero-image-opacity))}}:root{--vp-custom-block-tip-border: transparent;--vp-custom-block-tip-text: var(--vp-c-text-1);--vp-custom-block-tip-bg: var(--vp-c-brand-soft);--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft)}.DocSearch{--docsearch-primary-color: var(--vp-c-brand-1) !important}a{color:var(--vp-c-brand-1);text-decoration:none!important}.VPSidebarItem .text{padding:2px 0}html{overflow-y:scroll}.VPNavBarTitle .title{color:var(--logo-text-color)!important}.wide-page{--vp-layout-max-width: min(100vw, 2000px) }.wide-page .content-container{max-width:100%!important}td:has(>div[class*=language-]:only-child:not(.line-numbers-mode)){padding:0}td:has(>div[class*=language-]:only-child:not(.line-numbers-mode))>div{background-color:transparent!important;min-height:40px}td:has(>div[class*=language-]:only-child:not(.line-numbers-mode))>div:before{top:0;right:.5em}td:has(>div[class*=language-]:only-child:not(.line-numbers-mode))>div .copy{top:0}td:has(>div[class*=language-]:only-child:not(.line-numbers-mode)) .shiki{padding-top:0;padding-bottom:0}td:has(>div[class*=language-]:only-child:not(.line-numbers-mode)) .shiki code{padding-left:16px}.dark .light-only{display:none!important}html:not(.dark) .dark-only{display:none!important}table td li,table td ul,table td p{margin:0!important}table td>ul{padding-left:1rem!important}.code-tabs__nav{margin-top:.85rem;margin-bottom:-20px;padding-left:10px;border-top-left-radius:6px;border-top-right-radius:6px;background-color:var(--vp-code-block-bg)}.code-tabs__ul{margin:auto 0 5px;padding-left:0;display:inline-flex;list-style:none!important}.code-tabs__li{margin-top:0!important}.code-tabs__nav-tab{border:0;padding:5px 10px;cursor:pointer;background-color:transparent;font-size:.9em;line-height:1.4;color:#ffffffe6;font-weight:600}.code-tabs__nav-tab:focus{outline:none}.code-tabs__nav-tab:focus-visible{outline:1px solid rgba(255,255,255,.9)}.code-tabs__nav-tab-active{border-bottom:var(--vp-c-brand-1) 2px solid}@media (max-width: 500px){.code-tabs__nav{margin-left:-1.5rem;margin-right:-1.5rem;border-radius:0}}.code-tabs-item{color:#adbac7;background-color:var(--vp-code-block-bg)}.code-tabs-item div[class*=language-]:before{top:5px}.code-tabs-item>:not([class*=language-]){padding:0 24px}.code-tabs-item pre,.code-tabs-item pre+div{padding-top:8px!important}p+.code-prop{margin-top:36px}.code-prop{margin-top:10px;font-weight:inherit!important}.code-prop .shiki{margin:0;padding:2px 8px;white-space:normal;background-color:var(--vp-code-block-bg)!important;border-radius:5px}.code-prop .shiki .line{display:block;white-space:pre-wrap}.code-prop+:not(.code-prop,h1,h2,h3,h4,h5),.code-prop+:not(.code-prop,h1,h2,h3,h4,h5)+:not(.code-prop,h1,h2,h3,h4,h5),.code-prop+:not(.code-prop,h1,h2,h3,h4,h5)+:not(.code-prop,h1,h2,h3,h4,h5)+:not(.code-prop,h1,h2,h3,h4,h5),.code-prop+:not(.code-prop,h1,h2,h3,h4,h5)+:not(.code-prop,h1,h2,h3,h4,h5)+:not(.code-prop,h1,h2,h3,h4,h5)+:not(.code-prop,h1,h2,h3,h4,h5),.code-prop+:not(.code-prop,h1,h2,h3,h4,h5)+:not(.code-prop,h1,h2,h3,h4,h5)+:not(.code-prop,h1,h2,h3,h4,h5)+:not(.code-prop,h1,h2,h3,h4,h5)+:not(.code-prop,h1,h2,h3,h4,h5){margin-top:4px;margin-left:20px}.intellitect-footer{margin:auto;text-align:center}.intellitect-footer img{max-height:6ex;margin:auto}.intellitect-footer>.content{margin:auto;max-width:800px}.intellitect-footer.page-footer{font-size:14px}.intellitect-footer.page-footer hr{margin-bottom:20px!important}.intellitect-footer.page-footer *{line-height:1.75!important} diff --git a/assets/topics_audit-logging.md.m_cBN5t5.js b/assets/topics_audit-logging.md.m_cBN5t5.js new file mode 100644 index 000000000..b631cf71e --- /dev/null +++ b/assets/topics_audit-logging.md.m_cBN5t5.js @@ -0,0 +1,101 @@ +import{_ as s,o as a,c as n,R as e}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"Audit Logging","description":"","frontmatter":{},"headers":[],"relativePath":"topics/audit-logging.md","filePath":"topics/audit-logging.md"}'),o={name:"topics/audit-logging.md"},l=e(`

Audit Logging

Keeping a history of all (or most) of the changes that are made to records in your database can be invaluable, both for non-repudiation (i.e. proving what happened and who did it), and for troubleshooting or debugging.

Coalesce provides a package IntelliTect.Coalesce.AuditLogging that adds an easy way to inject this kind of audit logging into your EF Core DbContext. It also includes an out-of-the-box view c-admin-audit-log-page that enables browsing of this data on the frontend.

Setup

In this setup process, we're going to add an additional Coalesce Nuget package, define a custom entity to hold our audit logs, install the audit logging extension into our DbContext, and add a pre-made interface on the frontend to view our logs.

1. Add the NuGet package

Add a reference to the Nuget package IntelliTect.Coalesce.AuditLogging to your data project:

xml
<ItemGroup>
+  <PackageReference Include="IntelliTect.Coalesce.Vue" Version="$(CoalesceVersion)" />
+  <PackageReference Include="IntelliTect.Coalesce.AuditLogging" Version="$(CoalesceVersion)" />
+</ItemGroup>

2. Define the log entity

Define the entity type that will hold the audit records in your database:

c#
using IntelliTect.Coalesce.AuditLogging;
+
+[Read(Roles = "Administrator")]
+public class AuditLog : DefaultAuditLog
+{
+    public string? UserId { get; set; }
+    public AppUser? User { get; set; }
+
+    // Other custom props as desired
+}

This entity only needs to implement IAuditLog, but a default implementation of this interface DefaultAuditLog is provided for your convenience. DefaultAuditLog contains additional properties ClientIp, Referrer, and Endpoint for recording information about the HTTP request (if available), and also comes pre-configured for security with Create, Edit, and Delete APIs disabled.

You should further augment this type with any additional properties that you would like to track on each change record. A property to track the user who performed the change should be added, since it is not provided by the default implementation so that you can declare it yourself with the correct type for the foreign key and navigation property.

You should also apply security to restrict reading of these records to only the most privileged users with a Read Attribute (as in the example above) and/or a custom Default Data Source.

3. Configure your DbContext

On your DbContext, implement the IAuditLogDbContext<AuditLog> interface using the class you just created as the type parameter. Then register the Coalesce audit logging extension in your DbContext's OnConfiguring method so that saves will be intercepted and audit log entries created.

c#
[Coalesce]
+public class AppDbContext : DbContext, IAuditLogDbContext<AuditLog>
+{
+    public DbSet<AuditLog> AuditLogs { get; set; }
+    public DbSet<AuditLogProperty> AuditLogProperties { get; set; }
+
+    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+    {
+        optionsBuilder.UseCoalesceAuditLogging<AuditLog>(x => x
+            .WithAugmentation<OperationContext>()
+        );
+    }
+}

You could also perform this setup in your web project when calling .AddDbContext().

The above code also contains a reference to a class OperationContext. This is the service that will allow you to populate additional custom properties on your audit entries. You'll want to define it as follows:

c#
public class OperationContext : DefaultAuditOperationContext<AuditLog>
+{
+    // Inject any additional desired services in the constructor:
+    public OperationContext(IHttpContextAccessor accessor) : base(accessor) { }
+
+    public override void Populate(AuditLog auditEntry, EntityEntry changedEntity)
+    {
+        base.Populate(auditEntry, changedEntity);
+
+        // Adjust as needed to retrieve your UserId from the ClaimsPrincipal.
+        auditEntry.UserId = User.GetUserId();
+    }
+}

When you're inheriting from DefaultAuditLog for your IAuditLog implementation, you'll want to similarly inherit from DefaultAuditOperationContext<> for your operation context. It will take care of populating the HTTP request tracking fields on the AuditLog record. If you want a totally custom implementation, you only need to implement the IAuditOperationContext<TAuditLog> interface.

The operation context class passed to WithAugmentation will be injected from the application service provider if available; otherwise, a new instance will be constructed using dependencies from the application service provider. To make an injected dependency optional, make the constructor parameter nullable with a default value of null, or create alternate constructors.

4. Add the UI

For Vue applications, the c-admin-audit-log-page component provides an out-of-the-box user interface for browsing through audit logs. Simply define the following route in your application's router:

ts
import { CAdminAuditLogPage } from 'coalesce-vue-vuetify3';
+
+{
+  path: '/admin/audit-logs',
+  component: CAdminAuditLogPage,
+  props: { type: 'AuditLog' }
+}

Configuration

Suppression

You can turn audit logging on or off for individual operations by implementing the SuppressAudit property on your DbContext. For example, implement it as an auto-property as follows and then set it to true in application code when desired:

c#
[Coalesce]
+public class AppDbContext : DbContext, IAuditLogDbContext<AuditLog>
+{
+    ...
+    public bool SuppressAudit { get; set; }
+}

Exclusions & Formatting

Coalesce's audit logging is built on top of Entity Framework Plus and can be configured using all of its configuration, including includes/excludes and custom property formatting.

Coalesce will not use EF Plus's AuditManager.DefaultConfiguration global singleton instance. You must use Coalesce's configuration extensions which allow for more targeted configuration per context that does not rely on a global static singleton. For example:

c#
public class AppDbContext : DbContext, IAuditLogDbContext<AuditLog>
+{
+    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+    {
+        optionsBuilder.UseCoalesceAuditLogging<AuditLog>(x => x
+            .WithAugmentation<OperationContext>()
+            .ConfigureAudit(c => c
+                .Exclude<DataProtectionKey>()
+                .ExcludeProperty<TrackingBase>(x => new { x.CreatedById, x.CreatedOn, x.ModifiedById, x.ModifiedOn })
+                .FormatType<DateTimeOffset>(d => d.ToTimeZone("America/Los_Angeles").ToString())
+                .Format<Image>(x => x.Content, x => $"{Convert.ToHexString(SHA1.HashData(x))}, {x.Length} bytes")
+            )
+        );
+    }
+}

Property Descriptions

The AuditLogProperty children of your IAuditLog implementation have two properties OldValueDescription and NewValueDescription that can be used to hold a description of the old and new values. By default, Coalesce will populate the descriptions of foreign key properties with the List Text of the referenced principal entity. This greatly improves the usability of the audit logs, which would otherwise only show meaningless numbers or GUIDs for foreign keys that changed.

This feature will load principal entities into the DbContext if they are not already loaded, which could inflict subtle differences in application functionality in rare edge cases if your application is making assumptions about navigation properties not being loaded. Typically though, this will not be an issue and will not lead unintentional information disclosure to clients as long as IncludeTrees are used correctly.

This feature may be disabled by calling .WithPropertyDescriptions(PropertyDescriptionMode.None) inside your call to .UseCoalesceAuditLogging(...) in your DbContext configuration. You may also populate these descriptions in your IAuditOperationContext implementation that was provided to .WithAugmentation<T>().

Merging

When using a supported database provider (currently only SQL Server), audit records for changes to the same entity will be merged together when the change is identical in all aspects to the previous audit record for that entity, with the sole exception of the old/new property values.

In other words, if the same user is making repeated changes to the same property on the same entity from the same page, then those changes will merge together into one audit record.

This merging only happens together if the existing audit record is recent; the default cutoff for this is 30 seconds, but can be configured with .WithMergeWindow(TimeSpan.FromSeconds(15)) when calling UseCoalesceAuditLogging. It can also be turned off by setting this value to TimeSpan.Zero. The merging logic respects all custom properties you add to your IAuditLog implementation, requiring their values to match between the existing and new audit records for a merge to occur.

Caveats

Only changes that are tracked by the DbContext's ChangeTracker can be audited. Changes that are made with raw SQL, or changes that are made with bulk update functions like ExecuteUpdate or ExecuteDelete will not be audited using this package.

Audit Stamping

A lightweight alternative or addition to full audit logging is audit stamping - the process of setting fields like CreatedBy or ModifiedOn on each changed entity. This cannot record a history of exact changes, but can at least record the age of an entity and how recently it changed.

Coalesce offers a simple mechanism to register an Entity Framework save interceptor to perform this kind of action (this does NOT require the IntelliTect.Coalesce.AuditLogging package). This mechanism operations on all saves that go through Entity Framework, eliminating the need to perform this manually in individual Behaviors, Services, and Custom Methods:

c#
services.AddDbContext<AppDbContext>(options => options
+    .UseSqlServer(connectionString) // (or other provider)
+    .UseStamping<TrackingBase>((entity, user) => entity.SetTracking(user))
+);

In the above example, TrackingBase is an interface or class that you would write as part of your application that defines the properties and mechanisms for performing the tracking operation. For example:

c#
public abstract class TrackingBase
+{
+    [Read, Display(Order = 1000)]
+    public ApplicationUser CreatedBy { get; set; }
+    
+    [Read, Display(Order = 1001)]
+    public string? CreatedById { get; set; }
+    
+    [Read, Display(Order = 1002)]
+    public DateTimeOffset CreatedOn { get; set; }
+
+
+    [Read, Display(Order = 1003)]
+    public ApplicationUser ModifiedBy { get; set; }
+    
+    [Read, Display(Order = 1004)]
+    public string? ModifiedById { get; set; }
+    
+    [Read, Display(Order = 1005)]
+    public DateTimeOffset ModifiedOn { get; set; }
+
+
+    public void SetTracking(ClaimsPrincipal? user) 
+        => SetTracking(user?.GetApplicationUserId());
+    
+    public void SetTracking(int? userId)
+    {
+        if (this.CreatedById == null)
+        {
+            this.CreatedById = userId;
+            this.CreatedOn = DateTimeOffset.Now;
+        }
+
+        this.ModifiedById = userId;
+        this.ModifiedOn = DateTimeOffset.Now;
+    }
+}

The overload UseStamping<TStampable> will provide the ClaimsPrincipal from the current HTTP request if present, defaulting to null if an operation occurs outside an HTTP request (e.g. a background job). The overloads UseStamping<TStampable, TService> and UseStamping<TStampable, TService1, TService2> can be used to inject services into the operation. If more than two services are needed, you should wrap those dependencies up into an additional services that takes them as dependencies.

`,50),t=[l];function p(r,c,D,i,y,d){return a(),n("div",null,t)}const g=s(o,[["render",p]]);export{u as __pageData,g as default}; diff --git a/assets/topics_audit-logging.md.m_cBN5t5.lean.js b/assets/topics_audit-logging.md.m_cBN5t5.lean.js new file mode 100644 index 000000000..360e054a9 --- /dev/null +++ b/assets/topics_audit-logging.md.m_cBN5t5.lean.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as n,R as e}from"./chunks/framework.g9eZ-ZSs.js";const u=JSON.parse('{"title":"Audit Logging","description":"","frontmatter":{},"headers":[],"relativePath":"topics/audit-logging.md","filePath":"topics/audit-logging.md"}'),o={name:"topics/audit-logging.md"},l=e("",50),t=[l];function p(r,c,D,i,y,d){return a(),n("div",null,t)}const g=s(o,[["render",p]]);export{u as __pageData,g as default}; diff --git a/assets/topics_coalesce-json.md.U3jptpSC.js b/assets/topics_coalesce-json.md.U3jptpSC.js new file mode 100644 index 000000000..435ecfb63 --- /dev/null +++ b/assets/topics_coalesce-json.md.U3jptpSC.js @@ -0,0 +1,69 @@ +import{_ as s,o as n,c as a,R as e}from"./chunks/framework.g9eZ-ZSs.js";const f=JSON.parse('{"title":"Code Generation Configuration","description":"","frontmatter":{},"headers":[],"relativePath":"topics/coalesce-json.md","filePath":"topics/coalesce-json.md"}'),o={name:"topics/coalesce-json.md"},l=e(`

Code Generation Configuration

In Coalesce, all configuration of the code generation is done in a JSON file. This file is typically named coalesce.json and is typically placed in the solution root.

File Resolution

When the code generation is run by invoking dotnet coalesce, Coalesce will try to find a configuration file via the following means:

  1. If an argument is specified on the command line, it will be used as the location of the file. E.g. dotnet coalesce C:/Projects/MyProject/config.json
  2. If no argument is given, Coalesce will try to use a file in the working directory named coalesce.json
  3. If no file is found in the working directory, Coalesce will crawl up the directory tree from the working directory until a file named coalesce.json is found. If such a file is never found, an error will be thrown.

Contents

A full example of a coalesce.json file, along with an explanation of each property, is as follows:

js
{
+    "webProject": {
+        // Required: Path to the csproj of the web project. Path is relative to location of this coalesce.json file.
+        "projectFile": "src/Coalesce.Web/Coalesce.Web.csproj",
+
+        // Optional: Framework to use when evaluating & building dependencies.
+        // Not needed if your project only specifies a single framework - only required for multi-targeting projects.
+        "framework": "netcoreapp2.0",
+
+        // Optional: Build configuration to use when evaluating & building dependencies.
+        // Defaults to "Debug".
+        "configuration": "Debug",
+
+        // Optional: Override the namespace prefix for generated C# code.
+        // Defaults to MSBuild's \`$(RootNamespace)\` for the project.
+        "rootNamespace": "MyCompany.Coalesce.Web",
+    },
+
+    "dataProject": {
+        // Required: Path to the csproj of the data project. Path is relative to location of this coalesce.json file.
+        "projectFile": "src/Coalesce.Domain/Coalesce.Domain.csproj",
+
+        // Optional: Framework to use when evaluating & building dependencies.
+        // Not needed if your project only specifies a single framework - only required for multi-targeting projects.
+        "framework": "netstandard2.0",
+
+        // Optional: Build configuration to use when evaluating & building dependencies.
+        // Defaults to "Release".
+        "configuration": "Debug",
+    },
+
+    // The name of the root generator to use. Defaults to "Knockout".
+    // Available values are "Vue" and "Knockout".
+    "rootGenerator": "Vue",
+            
+    // If set, specifies a list of whitelisted root type names that will restrict
+    // which types Coalesce will use for code generation. 
+    // Root types are those that must be annotated with [Coalesce].
+    // Useful if want to segment a single data project into multiple web projects, 
+    // or into different areas/directories within a single web project.
+    "rootTypesWhitelist": [
+        "MyDbContext", "MyCustomDto"
+    ],
+
+    "generatorConfig": {
+        // A set of objects keyed by generator name.
+        // Generator names may optionally be qualified by their full namespace.
+        // All generators are listed when running 'dotnet coalesce' with '--verbosity debug'.
+        // For example, "Views" or "IntelliTect.Coalesce.CodeGeneration.Knockout.Generators.Views".
+        "GeneratorName": {
+            // Optional: true if the generator should be disabled.
+            "disabled": true,
+            // Optional: Configures a path relative to the default output path for the generator
+            // where that generator's output should be placed instead.
+            "targetDirectory": "../DifferentFolder"
+        },
+        // Indentation for generated C# is configurable by type (API controllers, DTO classes and regular View controllers)
+        // It defaults to 4 spaces
+        "ApiController": {
+            "indentationSize": 2 
+        },
+        "ClassDto": {
+            "indentationSize": 2 
+        },
+        "ViewController" : {
+            "indentationSize": 2
+        }
+    }
+}

Additional CLI Options

There are a couple of extra options which are only available as CLI parameters to dotnet coalesce. These options do not affect the behavior of the code generation - only the behavior of the CLI itself.

--debug

When this flag is specified when running dotnet coalesce, Coalesce will wait up to 60 seconds for a debugger to be attached to its process before starting code generation.

-v|--verbosity <level>

Set the verbosity of the output. Options are trace, debug, information, warning, error, critical, and none.

`,14),t=[l];function p(c,i,r,u,d,y){return n(),a("div",null,t)}const C=s(o,[["render",p]]);export{f as __pageData,C as default}; diff --git a/assets/topics_coalesce-json.md.U3jptpSC.lean.js b/assets/topics_coalesce-json.md.U3jptpSC.lean.js new file mode 100644 index 000000000..35979e8b5 --- /dev/null +++ b/assets/topics_coalesce-json.md.U3jptpSC.lean.js @@ -0,0 +1 @@ +import{_ as s,o as n,c as a,R as e}from"./chunks/framework.g9eZ-ZSs.js";const f=JSON.parse('{"title":"Code Generation Configuration","description":"","frontmatter":{},"headers":[],"relativePath":"topics/coalesce-json.md","filePath":"topics/coalesce-json.md"}'),o={name:"topics/coalesce-json.md"},l=e("",14),t=[l];function p(c,i,r,u,d,y){return n(),a("div",null,t)}const C=s(o,[["render",p]]);export{f as __pageData,C as default}; diff --git a/assets/topics_security.md.DU-P7c3D.js b/assets/topics_security.md.DU-P7c3D.js new file mode 100644 index 000000000..6f3304b2d --- /dev/null +++ b/assets/topics_security.md.DU-P7c3D.js @@ -0,0 +1,291 @@ +import{_ as s,o as a,c as e,R as n}from"./chunks/framework.g9eZ-ZSs.js";const o="/Coalesce/assets/security-overview.lqdZeXXG.webp",h=JSON.parse('{"title":"Security","description":"","frontmatter":{},"headers":[],"relativePath":"topics/security.md","filePath":"topics/security.md"}'),l={name:"topics/security.md"},t=n(`

Security

This page is a comprehensive overview of all the techniques that can be used in a Coalesce application to restrict the capabilities of API endpoints that Coalesce generates.

The following table is a quick reference of scenarios you might encounter and how you might handle them. If you're unfamiliar with these techniques, though, then you are encouraged to read through this page to get a deeper understanding of what's available before selecting a solution.

FeatureRestrictionTechnique

Entity Reads: /get,
/list,
/count

Disable

[Read(DenyAll)]

Roles

[Read("RoleName")]

Prevent auto-include

  • Omit base call in Data Source GetQuery override.
  • [Read(NoAutoInclude = true)] on properties or types.

Any custom code:

  • Query Predicates
  • Filtered Includes
  • Conditional Includes
  • Sort/search/filter overrides

Custom Default Data Source

Entity Mutations: /save,
/bulkSave,
/delete

Disable

[Create(DenyAll)] [Edit(DenyAll)] [Delete(DenyAll)]

Roles

[Create("Role")] [Edit("Role")] [Delete("Role")]

Restrict target records (edit/delete)

Custom Default Data Source

Static Validation

Validation attributes

Any custom code:

  • Security
  • Validation

Custom Behaviors

Methods and Services

Disable

N/A - explicit opt-in required via [Coalesce]

Roles

[Execute("RoleName")]

Static Validation

Validation attributes

Restrict Targets (only instance methods)

Other

Write custom logic in the method.

Properties
(All input and output for Entity CRUD, Methods, and Services)

Globally Exclude
Roles

Property Security Attributes

Read-only

Init-only (write-once)

init setter

Custom security

Endpoint Security

Coalesce generates API endpoints by traversing your data model's classes, starting from types annotated with [Coalesce]. This usually includes your DbContext class, as well as any Service classes or interfaces.

Classes can be hidden from Coalesce entirely by annotating them with [InternalUse], preventing generation of API endpoints for that class, as well as preventing properties of that type from being exposed.

DbSet<> properties on your DbContext class can also be annotated with [InternalUse], causing that type to be treated by Coalesce like an External Type rather than an Entity, once again preventing generation of API endpoints but without preventing properties of that type from being exposed.

Class Security Attributes

For each of your Entities and Custom DTOs, Coalesce generates a set of CRUD API endpoints (/get, /list, /count, /save, /bulkSave, and /delete).

The default behavior is that all endpoints require an authenticated user (anonymous users are rejected).

These endpoints can be secured by placing any or all of the [Read], [Create], [Edit], and [Delete] attributes on the the class. Each attribute can specify required roles for that action, or open that action to anonymous, unauthenticated users, or disable the endpoint entirely.

This security is applied to the generated controllers. The [Read] attribute on a class does not affect instances of that class when those instances are present as child properties of other types, since in those scenarios the data will be coming from a different endpoint on a different controller.

EndpointsGoverning Attributes

/get, /list, /count

c#
[ReadAttribute]

/save

c#
[CreateAttribute] // Affects saves of new entities
+[EditAttribute]   // Affects saves of existing entities

/delete

c#
[DeleteAttribute]

/bulkSave

c#
// Read permission required for the root entity:
+[ReadAttribute]
+
+// Control of each entity affected by the bulk save:
+[CreateAttribute]
+[EditAttribute]
+[DeleteAttribute]

Here are some examples of applying security attributes to an entity class. If a particular action doesn't need to be restricted, you can omit that attribute, but this example shows usages of all four:

c#
// Allow read access by unauthenticated, anonymous users:
+[Read(SecurityPermissionLevels.AllowAll)]
+// Allow creation of new entities by the Admin and HR roles (params string[] style):
+[Create("Admin", "HR")]
+// Allow editing of existing Employee entities by users with the Admin or HR roles (CSV style):
+[Edit("Admin,HR")]
+// Prohibit deletion of Employee entities
+[Delete(SecurityPermissionLevels.DenyAll)]
+public class Employee
+{
+    public int EmployeeId { get; set; }
+}

Method Security Attributes

To secure the endpoints generated for your Custom Methods and Services, the [Execute] attribute can be used to specify a set of required roles for that endpoint, or to open that endpoint to anonymous users.

The default behavior is that all endpoints require an authenticated user (anonymous users are rejected).

For example:

c#
public class Employee
+{
+    public int EmployeeId { get; set; }
+
+    [Coalesce, Execute("Payroll,HR")]
+    public void GiveRaise(int centsPerHour) {
+        // Only Payroll and HR users can call this method
+    }
+
+    [Coalesce, Execute(SecurityPermissionLevels.AllowAll)]
+    public void SendMessage(string message) {
+        // Anyone (even anonymous, unauthenticated users) can call this method.
+    }
+}

Property/Column Security

Security applied via attributes to properties in Coalesce affects all usages of that property across all Coalesce-generated APIs. This includes usages of that property on types that occur as children of other types, which is a spot where class-level or endpoint-level security generally does not apply. These attributes can be placed on the properties on your Entities and External Types to apply role-based restrictions to that property.

  • ReadAttribute limits the roles that can read values from that property in responses from the server.
  • EditAttribute limits the roles that can write values to that property in requests made to the server.
  • RestrictAttribute registers an implementation of IPropertyRestriction that allows for writing custom code to implement these restrictions.

This security is executed and enforced by the mapping that occurs in the generated DTOs, meaning it affects both entity CRUD APIs as well as Custom Methods. It is also checked by the Standard Data Source to prevent sorting, searching, and filtering by properties that a user is not permitted to read.

Internal Properties

Properties can be hidden from Coalesce entirely, either with the [InternalUse] attribute or non-public C# access modifiers.

The properties in the following example are hidden entirely from all Coalesce functionality and generated APIs:

c#
using IntelliTect.Coalesce.DataAnnotations;
+public class Employee
+{
+  // InternalUseAttribute hides anything from Coalesce.
+  [InternalUse]
+  public string Name { get; set; }
+
+  // Non-public C# access modifiers will hide properties from Coalesce:
+  internal decimal Salary { get; set; }
+
+  // Property's type is [InternalUse], so properties using that type are also internal.
+  public Department Department { get; set; }
+}
+
+[InternalUse]
+public class Department
+{
+  // All properties on an [InternalUse] type are non-exposed,
+  // since the parent type is not exposed.
+  public string Name { get; set; }
+}

Read-Only Properties

A property in Coalesce can be made read-only in any of the following ways:

c#
using IntelliTect.Coalesce.DataAnnotations;
+using System.ComponentModel;
+public class Employee
+{
+  // A property with a [Read] attribute but no [Edit] attribute is read-only:
+  [Read]
+  public string Name { get; set; }
+
+  // Payroll users and HR users can read this property. Nobody can edit it:
+  [Read("Payroll,HR")]
+  public decimal Salary { get; set; }
+
+  // Using System.ComponentModel.ReadOnlyAttribute:
+  [ReadOnly(true)]
+  public DateTime BirthDate { get; set; }
+
+  // Non-public setter:
+  public DateTime StartDate { get; internal set; }
+
+  // No setter:
+  public string EmploymentDuration => (DateTime.Now - StartDate).ToString();
+
+  // Edits denied:
+  [Edit(SecurityPermissionLevels.DenyAll)]
+  public string EmployeeNumber { get; set; }
+}

Role Restrictions

Reading and writing a property in Coalesce can be restricted by roles:

c#
using IntelliTect.Coalesce.DataAnnotations;
+public class Employee
+{
+  // A property with no attributes is readable and writable without restriction
+  public string Name { get; set; }
+
+  // When a [Read] and [Edit] attributes are both present,
+  // the read roles are required for edits in addition to any edit roles.
+  // Property is only readable by Payroll & HR,
+  // and is also only editable by Payroll & HR.
+  [Read("Payroll,HR"), Edit]
+  public DateTime BirthDate { get; set; }
+
+  // Property is readable by Payroll and HR, and editable only by Payroll.
+  [Read("Payroll", "HR"), Edit("Payroll")]
+  public decimal Salary { get; set; }
+
+  // Property is readable by Payroll, and editable only by a user who is both Payroll AND HR.
+  [Read("Payroll"), Edit("HR")]
+  public DateTime StartDate { get; set; }
+
+  // Init-only properties on entities can only be set by the first /save of the entity.
+  public string EmployeeNumber { get; init; }
+}

A few of the examples above point out that when a property is restricted for reading by roles, those roles are also required when editing that property. This is because it usually doesn't make sense for a user to change a value when they have no way of knowing what the original value was. If you have a situation where a property should be editable without knowing the original value, use a custom method on the model to accept and set the new value.

Custom Restrictions

In addition to role-based property restrictions, you can also define property restrictions that can execute custom code for each model instance if your logic require more nuanced decisions than can be made with roles.

c#
using IntelliTect.Coalesce.DataAnnotations;
+public class Employee 
+{
+  public int Id { get; set; }
+
+  [Read]
+  public string UserId { get; set; }
+
+  [Restrict<SalaryRestriction>]
+  public decimal Salary { get; set; }
+}
+
+public class SalaryRestriction(MyUserService userService) : IPropertyRestriction<Employee>
+{
+  public bool UserCanRead(IMappingContext context, string propertyName, Employee model)
+    => context.User.GetUserId() == model.UserId || userService.IsPayroll(context.User);
+
+  public bool UserCanWrite(IMappingContext context, string propertyName, Employee model, object incomingValue)
+    => userService.IsPayroll(context.User);
+
+  public bool UserCanFilter(IMappingContext context, string propertyName)
+    => userService.IsPayroll(context.User);
+}

Restriction classes support dependency injection, so you can inject any supplemental services needed to make a determination.

The UserCanRead method controls whether values of the restricted property will be mapped from model instances to the generated DTO. Similarly, UserCanWrite controls whether the property can be mapped back to the model instance from the generated DTO.

The UserCanFilter method has a default implementation that returns false, but can be implemented if there is an appropriate, instance-agnostic way to determine if a user can sort, search, or filter values of that property.

Multiple different restrictions can be placed on a single property; all of them must succeed for the operation to be permitted. Restrictions also stack on top of role attribute restrictions ([Read] and [Edit]).

Row-level Security

Data Sources

In Coalesce, Data Sources are the mechanism that you can extend to implement row-level security on your Entities and Custom DTOs.

Data Sources are used when fetching results for /get, /list, and /count endpoints, and when fetching the target or result of a /save, /bulkSave, or /delete, and when fetching the invocation target of an Instance Method.

By default, your entities will be fetched using the Standard Data Source, but you can declare a custom default data source for each of your entities to override this default functionality. The default functionality here includes the default loading behavior, a feature where the Standard Data Source automatically includes the immediate relationships of requested entities. This can be suppressed by overriding the GetQuery method on your custom data source and not calling the base method, or by placing [Read(NoAutoInclude = true)] on classes or navigation properties that you do not want automatically included.

For most use cases, all your security rules will be implemented in the GetQuery/GetQueryAsync method. This is the most foundational method of the data source that all other functions in the data source build upon. Any predicates applied to the query of a type's default data source will affect all of the type's generated API endpoints (except for static custom methods).

There are a few different techniques that you can use to apply filtering in a data source, each one working for a specific use case. The example below includes an example of each technique.

Query Predicates

The Query Predicates technique involves applying a .Where() predicate to your query to filter the root entities that are returned by the query using some database-executed logic. This is a form of row-level security and can be used to only include a record based on the values of that record in the database.

Conditional Includes

The Conditional Includes technique involves conditionally appending .Include() calls to your query only when some server-executed criteria is met. Usually this involves checking the roles of a user and only including a navigation property if the user is in the requisite role. This technique cannot be used with database-executed logic and is therefore behaves more like table-level security than row-level security.

Filtered Includes

The Filtered Includes technique involves using EF Core filtered includes to apply database-executed logic to filter the rows of child collection navigation properties.

EF filtered Includes cannot be used to apply database-executed filters to reference navigation properties due to lack of EF support - see the sections below on transform results and global query filters for two possible solutions.

A complex example using all three of the above techniques:

c#
public class Employee
+{
+  public int EmployeeId { get; set; }
+  public bool IsIntern { get; set; }
+  public List<DepartmentMember> DepartmentMembers { get; set; }
+
+  // Override the default data source for Employee with a custom one:
+  [DefaultDataSource]
+  public class DefaultSource : StandardDataSource<Employee, AppDbContext>
+  {
+    public DefaultSource(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override IQueryable<Employee> GetQuery(IDataSourceParameters parameters) {
+      IQueryable<Employee> query = Db.Employees;
+
+      // TECHNIQUE: Conditional Includes - subset child objects using server-executed logic:
+      if (User.IsInRole("HR")) {
+        // HR can see everything. Return early so they are not subjected to the other filters:
+        return query.Include(e => e.DepartmentMembers).ThenInclude(dm => dm.Department);
+      }
+
+      // TECHNIQUE: Query Predicates - subset root objects using database-executed logic:
+      int employeeId = User.GetEmployeeId();
+      query = query.Where(e =>
+          // Anyone can see interns
+          e.IsIntern ||
+          // Otherwise, a user can only see employees in their own departments:
+          e.DepartmentMembers.Any(dm => dm.Department.DepartmentMembers.Any(u => u.EmployeeId == employeeId))
+        );
+
+      // TECHNIQUE: EF Core Filtered Includes - subset collections using database-executed logic.
+      // Include the departments of employees, but only those that the current user is a member of.
+      query = query.Include(e => e.DepartmentMembers
+        .Where(dm => dm.Department.DepartmentMembers.Any(u => u.EmployeeId == employeeId)))
+        .ThenInclude(dm => dm.Department);
+
+      return query;
+    }
+  }
+}
+
+public class Department
+{
+  public int DepartmentId { get; set; }
+  public string Name { get; set; }
+  public List<DepartmentMember> DepartmentMembers { get; set; }
+
+  // Override the default data source for Department with a custom one:
+  [DefaultDataSource]
+  public class DefaultSource : StandardDataSource<Department, AppDbContext>
+  {
+    public DefaultSource(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override IQueryable<Department> GetQuery(IDataSourceParameters parameters) {
+      IQueryable<Department> query = Db.Departments
+        .Include(e => e.DepartmentMembers).ThenInclude(dm => dm.Employee);
+
+      if (!User.IsInRole("HR"))
+      {
+        // Non-HR users can only see their own departments:
+        query = query.Where(d => d.DepartmentMembers.Any(dm => dm.EmployeeId == User.GetEmployeeId()));
+      }
+
+      return query;
+    }
+  }
+}
+
+// Only HR can directly read or modify DepartmentMember records.
+[Read("HR"), Create("HR"), Edit("HR"), Delete("HR")]
+public class DepartmentMember
+{
+  public int Id { get; set; }
+
+  public int DepartmentId { get; set; }
+  public Department Department { get; set; }
+  public int EmployeeId { get; set; }
+  public Employee Employee { get; set; }
+}

Transform Results

There exists a fourth technique in Data Sources for applying filtered includes: the TransformResultsAsync method. Unlike the other techniques above that are performed in the GetQuery method and applied at the beginning of the data source query pipeline, TransformResults is applied at the very end of the process against the materialized results. It also only affects the responses from the generated /get, /list, /save, /bulkSave, and /delete endpoints - it has no bearing on the invocation target of instance methods.

The primary purpose of TransformResults is to conditionally load navigation properties. This was very useful before EF Core introduced native filtered includes for collection navigation properties, and is still useful for applying filtered includes to reference navigation properties since EF does not support this. It can also be used for any kind of filtered includes if native EF filtered includes get translated into poorly-performant SQL, or it can be used to populate external type or other non-database-mapped properties on your entities.

The general technique for using TransformResults involves using EF Core Explicit Loading to attach additional navigation properties to the result set, and then using Coalesce's .IncludedSeparately() method in the data source's GetQuery so that Coalesce can still build the correct Include Tree to shape the serialization of your results.

c#
public class Employee
+{
+  public int EmployeeId { get; set; }
+  public int ManagerId { get; set; }
+  public Employee Manager { get; set; }
+
+  [DefaultDataSource]
+  public class DefaultSource : StandardDataSource<Employee, AppDbContext>
+  {
+    public DefaultSource(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override IQueryable<Employee> GetQuery(IDataSourceParameters parameters)
+      // Use IncludedSeparately to instruct Coalesce that we're going to
+      // manually populate the Manager, and that it should be mapped to the result DTOs
+      // despite not being eagerly loaded with EF's .Include() method.
+      => Db.Employees.IncludedSeparately(e => e.Manager);
+
+    public override async Task TransformResultsAsync(
+      IReadOnlyList<Employee> results,
+      IDataSourceParameters parameters
+    )
+    {
+      foreach (var employee in results)
+      {
+        // Only load the employee's manager if the current logged in user is that manager.
+        if (employee.ManagerId == User.GetEmployeeId() && employee.Manager is null) {
+          await Db.Employees.Where(e => e.EmployeeId == employee.ManagerId).LoadAsync();
+        }
+      }
+    }
+  }
+}

Alternatively, and indeed preferably, you can often formulate a query that does not use iteration and requires only a single database round-trip:

c#
public override async Task TransformResultsAsync(
+  IReadOnlyList<Employee> results,
+  IDataSourceParameters parameters
+)
+{
+  var managerIds = results.Select(e => e.ManagerId).ToList();
+  await Db.Employees
+    .Where(e => managerIds.Contains(e.ManagerId) && e.EmployeeId == User.GetEmployeeId())
+    .LoadAsync();
+}

Behaviors

In Coalesce, Behaviors are the extension point to implement row-level security or other customizations of create/edit/delete operations on your Entities and Custom DTOs. Behaviors are implemented on top of data sources, meaning the client request will be rejected if the requested entity for modification cannot be loaded from the entity's default data source.

By default, each entity will use the Standard Behaviors, but you can declare a custom behaviors class for each of your entities to override this default functionality.

For most use cases, all your security rules will be implemented in the BeforeSave/BeforeSaveAsync and BeforeDelete/BeforeDeleteAsync methods.

For a more complete explanation of everything you can do with behaviors, see the full Behaviors documentation page.

EF Global Query Filters

Since Coalesce's data access layer is built on top of Entity Framework, you can also use Entity Framework's Global Query Filters feature to apply row-level security.

This approach is less flexible than custom Coalesce data sources and has other drawbacks as well, but on the other hand it has more absolute authority, is less susceptible to issues like inadvertently returning data through unfiltered navigation properties, and can sometimes require less work to implement than individual data sources.

Global Query Filters are also the only way to implement database-executed filtered includes of reference navigation properties, as there is no version of .Include() for reference navigation properties that allows a database-executed predicate to be applied. See this open issue on EF Core.

Foreign Key Injection Vulnerabilities

When a user is saving a model with Coalesce, they can provide values for the model's foreign key properties. When this interaction takes place through a user interface, the user is not likely to produce a foreign key referencing an object that the user is not allowed to view.

A malicious user, however, is a different story. Imagine a user who is brute-forcing the /save endpoint on one of your entities, enumerating values of a foreign key. The may be trying to leak data through navigation property values returned by the response from the save, or they may be trying to inject their data into an object graph that they do not otherwise have access to.

If this scenario sounds like a plausible threat vector your application, be sure to perform sufficient validation of incoming foreign keys to ensure that the user is allowed to use a particular foreign key value before saving it to your database.

Also consider making any required foreign keys that should not change for the lifetime of an entity into init-only properties (i.e. use the init accessor in C# instead of the set accessor). While this does not entirely solve the foreign key injection issue, it eliminates the need to validate that a user is not changing the parent of an object if such an operation is not desirable.

Server-side Data Validation

Coalesce, as of version 4, will by default perform server-side validation of incoming data using validation attributes.

Your database will also enforce any constraints (referential integrity, not null, check constraints, etc.), but errors produced by your database will manifest as exceptions, which are not user-friendly.

For any custom validation that cannot be implemented by attributes, you must implement that yourself for saves and deletes or custom methods.

Attribute Validation

Historically, Coalesce did not provide any automatic, attribute-based validation of incoming data. As of Coalesce 4.0, automatic server side validation using ValidationAttribute-derived attributes on your models is enabled by default.

In addition to any validation attributes present on your model properties and method parameters, there are some other rules that work similarly to the default validation in ASP.NET Core:

  • The C# 11 required keyword also acts like a RequiredAttribute
  • If C# nullable reference types are enabled, non-nullable reference types are required required.
  • Non-nullable value types are implicitly optional, with the exception of non-nullable foreign keys, which are required.

To disable this functionality for your entire application, disable the corresponding configuration options on CoalesceOptions. For example, in Startup.cs or Program.cs:

c#
services.AddCoalesce<AppDbContext>(b => b.Configure(o =>
+{
+    // Set either to false to disable:
+    o.ValidateAttributesForSaves = true;
+    o.ValidateAttributesForMethods = true;
+}));

Each option also has a more granular override:

Enabling ValidateAttributesForSaves causes the Standard Behaviors to perform validation of validation attributes during /save or /bulkSave calls, preventing a save when validation fails. This can be overridden per type or even per request by setting the ValidateAttributesForSaves property on a custom Behaviors instance.

Enabling ValidateAttributesForMethods causes the generated controllers for custom methods to perform validation of incoming parameters. Validation attributes may be placed on method parameters, and validation will also be performed against the members of any complex type parameters. This can be overridden per method by setting the ValidateAttributes property on ExecuteAttribute for the method.

Saves and Deletes

Validation of /save, /bulkSave, and /delete actions against Entities and Custom DTOs are performed by the Behaviors for the type. Automatic attribute based validation can be used (saves only), or Behaviors can be overridden to perform validation and other customization of the save and delete process, as in the following example:

c#
public class Employee
+{
+  public int IsCeo { get; set; }
+  public decimal Salary { get; set; }
+
+  [Coalesce]
+  public class Behaviors : StandardBehaviors<Employee, AppDbContext>
+  {
+    public Behaviors(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override ItemResult BeforeSave(SaveKind kind, Employee? oldItem, Employee item)
+    {
+      // \`oldItem\` is a shallow copy of entity from the database,
+      // and \`item\` is the tracked entity with incoming user data applied to it.
+      if (item.Salary > 1_000_000m && !oldItem.IsCeo) return "Salary is too high.";
+      return true;
+    }
+
+    public override ItemResult BeforeDelete(Case item)
+    {
+      if (item.IsCeo) return "The CEO cannot be fired.";
+      return true;
+    }
+  }
+}

Custom Methods and Services

For Custom Methods and Services, you can perform your own custom validation and return errors when validation fails. You can also use attribute based validation. Custom methods that need to return errors to the client are recommended to wrap their return type in an ItemResult<T>, allowing errors to be received and handled elegantly by your Coalesce Typescript code.

c#
public class Employee
+{
+  public decimal Salary { get; set; }
+
+  [Coalesce]
+  public ItemResult<decimal> GiveRaise(decimal raiseAmount)
+  {
+    if (raiseAmount > 3.5m) return "Raises must be less than $3.50."
+    Salary += raiseAmount;
+    return Salary;
+  }
+}

Security Overview Page

Coalesce provides batteries-included page that you can view to review the effective security rules in place for all the Coalesce-generated code in your project. Add this page to your application by mapping it as a route, either directly on WebHost in .NET 6+, or in UseEndpoints for 3.1+.

TIP

If you include the security overview in your production app, you should secure it with an authorization policy like in the example below. Alternatively, only map the endpoint in non-production environments.

c#
// .NET 6+ Program.cs:
+app.MapCoalesceSecurityOverview("coalesce-security").RequireAuthorization(
+    new AuthorizeAttribute { Roles = env.IsDevelopment() ? null : "Admin" }
+);
+
+// .NET Core 3.1+ Startup.cs:
+app.UseEndpoints(endpoints =>
+{
+    endpoints.MapCoalesceSecurityOverview("coalesce-security").RequireAuthorization(
+        new AuthorizeAttribute { Roles = env.IsDevelopment() ? null : "Admin" }
+    );
+});

Example of the contents of the security overview page:

Testing Your Security

If your application has complex security requirements and/or sensitive data that needs to be protected, you are encouraged to invest time into creating a set of automated tests to ensure that it is working how you expect.

The most comprehensive way to do this is to build a suite of integration tests using Microsoft's in-memory test server infrastructure. Follow Microsoft's documentation to set up a test project, and then write tests against your API endpoints. You will want to substitute your Entity Framework database provider with an in-memory Sqlite instance, and add a mock authentication handler to simulate authentication (we're mainly focused on testing authorization, not authentication).

',107),p=[t];function r(c,D,i,y,d,C){return a(),e("div",null,p)}const m=s(l,[["render",r]]);export{h as __pageData,m as default}; diff --git a/assets/topics_security.md.DU-P7c3D.lean.js b/assets/topics_security.md.DU-P7c3D.lean.js new file mode 100644 index 000000000..23998036e --- /dev/null +++ b/assets/topics_security.md.DU-P7c3D.lean.js @@ -0,0 +1 @@ +import{_ as s,o as a,c as e,R as n}from"./chunks/framework.g9eZ-ZSs.js";const o="/Coalesce/assets/security-overview.lqdZeXXG.webp",h=JSON.parse('{"title":"Security","description":"","frontmatter":{},"headers":[],"relativePath":"topics/security.md","filePath":"topics/security.md"}'),l={name:"topics/security.md"},t=n("",107),p=[t];function r(c,D,i,y,d,C){return a(),e("div",null,p)}const m=s(l,[["render",r]]);export{h as __pageData,m as default}; diff --git a/assets/topics_startup.md.rzuiSq70.js b/assets/topics_startup.md.rzuiSq70.js new file mode 100644 index 000000000..e4f10494e --- /dev/null +++ b/assets/topics_startup.md.rzuiSq70.js @@ -0,0 +1,28 @@ +import{_ as o,D as l,o as p,c as t,I as n,R as c,k as s,a}from"./chunks/framework.g9eZ-ZSs.js";const F=JSON.parse('{"title":"Application Configuration","description":"","frontmatter":{},"headers":[],"relativePath":"topics/startup.md","filePath":"topics/startup.md"}'),r={name:"topics/startup.md"},D=c(`

Application Configuration

In order for Coalesce to work in your application, you must register the needed services in your Startup.cs or Program.cs. Doing so is simple:

c#
public void ConfigureServices(IServiceCollection services)
+{
+    services.AddCoalesce<AppDbContext>();
+    ...
+}

This registers all the basic services that Coalesce needs in order to work with your EF DbContext. However, there are many more options available. Here's a more complete invocation of AddCoalesce that takes advantage of many of the options available:

c#
public void ConfigureServices(IServiceCollection services)
+{
+    services.AddCoalesce(builder => builder
+        .AddContext<AppDbContext>()
+        .UseDefaultDataSource(typeof(MyDataSource<,>))
+        .UseDefaultBehaviors(typeof(MyBehaviors<,>))
+        .UseTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"))
+        .Configure(o =>
+        {
+            o.ValidateAttributesForMethods = true; // note: true is the default
+            o.ValidateAttributesForSaves = true; // note: true is the default
+            o.DetailedExceptionMessages = true;
+            o.ExceptionResponseFactory = ctx =>
+            {
+                if (ctx.Exception is FileNotFoundException)
+                {
+                    ctx.HttpContext.Response.StatusCode = 404; // Optional - set a specific response code.
+                    return new IntelliTect.Coalesce.Models.ApiResult(false, "File not found");
+                }
+                return null;
+            };
+        });
+    );
+}

Available builder methods include:

`,6),i=s("p",null,[a("Register services needed by Coalesce to use the specified context. This is done automatically when calling the "),s("code",null,"services.AddCoalesce();"),a(" overload.")],-1),y=s("p",null,[a("Overrides the default data source used, replacing the "),s("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-data-source"},"Standard Data Source"),a(". See "),s("a",{href:"/Coalesce/modeling/model-components/data-sources.html"},"Data Sources"),a(" for more details.")],-1),d=s("p",null,[a("Overrides the default behaviors used, replacing the "),s("a",{href:"/Coalesce/modeling/model-components/behaviors.html#standard-behaviors"},"Standard Behaviors"),a(". See "),s("a",{href:"/Coalesce/modeling/model-components/behaviors.html"},"Behaviors"),a(" for more details.")],-1),C=s("p",null,"Specify a static time zone that should be used when Coalesce is performing operations on dates/times that lack timezone information. For example, when a user inputs a search term that contains only a date, Coalesce needs to know what timezone's midnight to use when performing the search.",-1),u=s("p",null,"Specify a service implementation to use to resolve the current timezone. This should be a scoped service, and will be automatically registered if it is not already. This allows retrieving timezone information on a per-request basis from HTTP headers, Cookies, or any other source.",-1),h=s("p",null,"Configure additional options for Coalesce runtime behavior. Current options include options for server-side validation, and options for exception handling. See individual members for details.",-1);function m(f,v,A,g,E,b){const e=l("Prop");return p(),t("div",null,[D,n(e,{def:"public Builder AddContext()"}),i,n(e,{def:"public Builder UseDefaultDataSource(Type dataSource)"}),y,n(e,{def:"public Builder UseDefaultBehaviors(Type behaviors)"}),d,n(e,{def:"public Builder UseTimeZone(TimeZoneInfo timeZone)"}),C,n(e,{def:"public Builder UseTimeZone()"}),u,n(e,{def:"public Builder Configure(Action setupAction)"}),h])}const B=o(r,[["render",m]]);export{F as __pageData,B as default}; diff --git a/assets/topics_startup.md.rzuiSq70.lean.js b/assets/topics_startup.md.rzuiSq70.lean.js new file mode 100644 index 000000000..7d1669579 --- /dev/null +++ b/assets/topics_startup.md.rzuiSq70.lean.js @@ -0,0 +1 @@ +import{_ as o,D as l,o as p,c as t,I as n,R as c,k as s,a}from"./chunks/framework.g9eZ-ZSs.js";const F=JSON.parse('{"title":"Application Configuration","description":"","frontmatter":{},"headers":[],"relativePath":"topics/startup.md","filePath":"topics/startup.md"}'),r={name:"topics/startup.md"},D=c("",6),i=s("p",null,[a("Register services needed by Coalesce to use the specified context. This is done automatically when calling the "),s("code",null,"services.AddCoalesce();"),a(" overload.")],-1),y=s("p",null,[a("Overrides the default data source used, replacing the "),s("a",{href:"/Coalesce/modeling/model-components/data-sources.html#standard-data-source"},"Standard Data Source"),a(". See "),s("a",{href:"/Coalesce/modeling/model-components/data-sources.html"},"Data Sources"),a(" for more details.")],-1),d=s("p",null,[a("Overrides the default behaviors used, replacing the "),s("a",{href:"/Coalesce/modeling/model-components/behaviors.html#standard-behaviors"},"Standard Behaviors"),a(". See "),s("a",{href:"/Coalesce/modeling/model-components/behaviors.html"},"Behaviors"),a(" for more details.")],-1),C=s("p",null,"Specify a static time zone that should be used when Coalesce is performing operations on dates/times that lack timezone information. For example, when a user inputs a search term that contains only a date, Coalesce needs to know what timezone's midnight to use when performing the search.",-1),u=s("p",null,"Specify a service implementation to use to resolve the current timezone. This should be a scoped service, and will be automatically registered if it is not already. This allows retrieving timezone information on a per-request basis from HTTP headers, Cookies, or any other source.",-1),h=s("p",null,"Configure additional options for Coalesce runtime behavior. Current options include options for server-side validation, and options for exception handling. See individual members for details.",-1);function m(f,v,A,g,E,b){const e=l("Prop");return p(),t("div",null,[D,n(e,{def:"public Builder AddContext()"}),i,n(e,{def:"public Builder UseDefaultDataSource(Type dataSource)"}),y,n(e,{def:"public Builder UseDefaultBehaviors(Type behaviors)"}),d,n(e,{def:"public Builder UseTimeZone(TimeZoneInfo timeZone)"}),C,n(e,{def:"public Builder UseTimeZone()"}),u,n(e,{def:"public Builder Configure(Action setupAction)"}),h])}const B=o(r,[["render",m]]);export{F as __pageData,B as default}; diff --git a/coalesce-horizontal-color.svg b/coalesce-horizontal-color.svg new file mode 100644 index 000000000..fb32d410d --- /dev/null +++ b/coalesce-horizontal-color.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/coalesce-icon-color.svg b/coalesce-icon-color.svg new file mode 100644 index 000000000..fdcfcb93b --- /dev/null +++ b/coalesce-icon-color.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + diff --git a/concepts/include-tree.html b/concepts/include-tree.html new file mode 100644 index 000000000..bb050878e --- /dev/null +++ b/concepts/include-tree.html @@ -0,0 +1,136 @@ + + + + + + Include Tree | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Include Tree

When Coalesce maps from the your POCO objects that are returned from EF Core queries, it will follow a structure called an IncludeTree to determine what relationships to follow and how deep to go in re-creating that structure in the mapped DTOs.

Purpose

Without an IncludeTree present, Coalesce will map the entire object graph that is reachable from the root object. This can often spiral out of control if there aren't any rules defining how far to go while turning this graph into a tree.

For example, suppose you had the following model with a many-to-many relationship (key properties omitted for brevity):

c#
public class Employee
+{
+    [ManyToMany("Projects")]
+    public ICollection<EmployeeProject> EmployeeProjects { get; set; }
+            
+    public static IQueryable<Employee> WithProjectsAndMembers(AppDbContext db, ClaimsPrincipal user)
+    {
+        // Load all projects of an employee, as well as all members of those projects.
+        return db.Employees
+            .Include(e => e.EmployeeProjects)
+                .ThenInclude(ep => ep.Project.EmployeeProjects)
+                .ThenInclude(ep => ep.Employee);
+    }
+}
+
+public class Project
+{
+    [ManyToMany("Employees")]
+    public ICollection<EmployeeProject> EmployeeProjects { get; set; }
+}
+
+public class EmployeeProject
+{
+    public Employee Employee { get; set; }
+    public Project Project { get; set; }
+}

Now, imagine that you have five employees and five projects, with every employee being a member of every project (i.e. there are 25 EmployeeProject rows).

Your client code makes a call to the Coalesce-generated API to load Employee #1 using the custom data source:

ts
import { Employee } from '@/viewmodels.g'
+import { EmployeeViewModel } from '@/viewmodels.g'
+
+var employee = new EmployeeViewModel();
+employee.$dataSource = new Employee.DataSources.WithProjectsAndMembers();
+employee.$load(1);

If you're already familiar with the fact that an IncludeTree is implicitly created in this scenario, then imagine for a moment that this is not the case (if you're not familiar with this fact, then keep reading!).

After Coalesce has called your Data Sources and evaluated the EF IQueryable returned, there are now 35 objects loaded into the current DbContext being used to handle this request - the 5 employees, 5 projects, and 25 relationships.

To map these objects to DTOs, we start with the root (employee #1) and expand outward from there until the entire object graph has been faithfully re-created with DTO objects, including all navigation properties.

The root DTO object (employee #1) then eventually is passed to the JSON serializer by ASP.NET Core to formulate the response to the request. As the object is serialized to JSON, the only objects that are not serialized are those that were already serialized as an ancestor of itself. What this ultimately means is that the structure of the serialized JSON with our example scenario ends up following a pattern like this (the vast majority of items have been omitted):

Employee#1
+    EmployeeProject#1
+        Project#1
+            EmployeeProject#6
+                Employee#2
+                    EmployeeProject#7
+                        Project#2
+                            ... continues down through all remaining employees and projects.
+                    ...
+            EmployeeProject#11
+                Employee#3
+            ...
+    EmployeeProject#2
+        Project#2
+    ...

See how the structure includes the EmployeeProjects of Employee#2? We didn't write our custom data source calls to .Include in such a way that indicated that we wanted the root employee, their projects, the employees of those projects, and then the projects of those employees. But, because the JSON serializer blindly follows the object graph, that's what gets serialized. It turns out that the depth of the tree increases on the order of O(n^2), and the total size increases on the order of Ω(n!).

This is where IncludeTree comes in. When you use a custom data source like we did above, Coalesce automatically captures the structure of the calls to .Include and .ThenInclude, and uses this to perform trimming during creation of the DTO objects.

With an IncludeTree in place, our new serialized structure looks like this:

Employee#1
+    EmployeeProject#1
+        Project#1
+            EmployeeProject#6
+                Employee#2
+            EmployeeProject#11
+                Employee#3
+            ...
+    EmployeeProject#2
+        Project#2
+    ...

No more extra data trailing off the end of the projects' employees!

Usage

Custom Data Sources

In most cases, you don't have to worry about creating an IncludeTree. When using the Standard Data Source (or a derivative), the structure of the .Include and .ThenInclude calls will be captured automatically and be turned into an IncludeTree.

However, there are sometimes cases where you perform complex loading in these methods that involves loading data into the current DbContext outside of the IQueryable that is returned from the method. The most common situation for this is needing to conditionally load related data - for example, load all children of an object where the child has a certain value of a Status property.

In these cases, Coalesce provides a pair of extension methods, .IncludedSeparately and .ThenIncluded, that can be used to merge in the structure of the data that was loaded separately from the main IQueryable.

For example:

c#
public override IQueryable<Employee> GetQuery()
+{
+    // Load all projects that are complete, and their members, into the db context.
+    Db.Projects
+        .Include(p => p.EmployeeProjects).ThenInclude(ep => ep.Employee)
+        .Where(p => p.Status == ProjectStatus.Complete)
+        .Load();
+
+    // Return an employee query, and notify Coalesce that we loaded the projects in a different query.
+    return Db.Employees
+        .IncludedSeparately(e => e.EmployeeProjects)
+        .ThenIncluded(ep => ep.Project.EmployeeProjects)
+        .ThenIncluded(ep => ep.Employee);
+}

You can also override the GetIncludeTree method of the Standard Data Source to achieve the same result:

c#
public override IncludeTree GetIncludeTree(IQueryable<T> query, IDataSourceParameters parameters) => Db
+    .Employees
+    .IncludedSeparately(e => e.EmployeeProjects)
+    .ThenIncluded(ep => ep.Project.EmployeeProjects)
+    .ThenIncluded(ep => ep.Employee)
+    .GetIncludeTree();

Model Methods

If you have custom methods that return object data, you may also want to control the structure of the returned data when it is serialized. Fortunately, you can also use IncludeTree in these situations. Without an IncludeTree, the entire object graph is traversed and serialized without limit.

TIP

An IncludeTree can be obtained from any IQueryable by calling the GetIncludeTree extension method (using IntelliTect.Coalesce.Helpers.IncludeTree).

In situations where your root object isn't on your DbContext (see External Types), you can use Enumerable.Empty<MyNonDbClass>().AsQueryable() to get an IQueryable to start from. When you do this, you must use IncludedSeparately - the regular EF Include method won't work without a DbSet.

See the following two techniques for returning an IncludeTree from a custom method:

ItemResult.IncludeTree

The easiest and most versatile way to return an IncludeTree from a custom method is to make that method return an ItemResult<T>, and then set the IncludeTree property of the ItemResult object. For example:

c#
public class Employee
+{
+    public async Task<ItemResult<ICollection<Employee>>> GetChainOfCommand(AppDbContext db)
+    {
+        IQueryable<Employee> query = db.Employees
+            .Include(e => e.Supervisor);
+
+        var ret = new List<Employee>();
+        var current = this;
+        while (current.Supervisor != null)
+        {
+            ret.Push(current);
+            current = await query.FirstOrDefaultAsync(e => e.EmployeeId == current.SupervisorId);
+        }
+
+        return new(ret, includeTree: query.GetIncludeTree());
+    }
+}

Out Parameter

To tell Coalesce about the structure of the data returned from a model method, you can also add out IncludeTree includeTree to the signature of the method. Inside your method, set includeTree to an instance of an IncludeTree. However, this approach cannot be used on async methods, since out parameters are not allowed on async methods in C#. For example:

c#
public class Employee
+{
+    public ICollection<Employee> GetChainOfCommand(AppDbContext db, out IncludeTree includeTree)
+    {
+        IQueryable<Employee> query = db.Employees
+            .Include(e => e.Supervisor);
+
+        var ret = new List<Employee>();
+        var current = this;
+        while (current.Supervisor != null)
+        {
+            ret.Push(current);
+            current = query.FirstOrDefault(e => e.EmployeeId == current.SupervisorId);
+        }
+
+        includeTree = query.GetIncludeTree();
+
+        return ret;
+    }
+}

External Type Caveats

One important point remains regarding IncludeTree - it is not used to control the serialization of objects which are not mapped to the database, known as External Types. External Types are always put into the DTOs when encountered (unless otherwise prevented by [DtoIncludes] & [DtoExcludes] or Security Attributes), with the assumption that because these objects are created by you (as opposed to Entity Framework), you are responsible for preventing any undesired circular references.

By not filtering unmapped properties, you as the developer don't need to account for them in every place throughout your application where they appear - instead, they 'just work' and show up on the client as expected.

Note also that this statement does not apply to database-mapped objects that hang off of unmapped objects - any time a database-mapped object appears, it will be controlled by your include tree. If no include tree is present (because nothing was specified for the unmapped property), these mapped objects hanging off of unmapped objects will be serialized freely and with all circular references, unless you include some calls to .IncludedSeparately(m => m.MyUnmappedProperty.MyMappedProperty) to limit those objects down.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/concepts/includes.html b/concepts/includes.html new file mode 100644 index 000000000..4dfb2227e --- /dev/null +++ b/concepts/includes.html @@ -0,0 +1,76 @@ + + + + + + Includes String | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Includes String

Coalesce provides a number of extension points for loading & serialization which make use of a concept called an "includes string" (also referred to as "include string" or just "includes").

Includes String

The includes string is simply a string which can be set to any arbitrary value. It is passed from the client to the server in order to customize data loading and serialization. It can be set on both the TypeScript ViewModels and the ListViewModels.

ts
import { PersonViewModel, PersonListViewModel } from '@/viewmodels.g'
+
+var person = new PersonViewModel();
+person.$includes = "details";
+
+var personList = new PersonListViewModel();
+personList.$includes = "details";

The default value (i.e. no action) is the empty string.

Special Values

There are a few values of includes that are either set by default in the auto-generated views, or otherwise have special meaning:

ValueDescription
'none'Setting includes to none suppresses the Default Loading Behavior provided by the Standard Data Source - The resulting data will be the requested object (or list of objects) and nothing more.
'admin-list'Used when loading a list of objects in the Vue admin list page.
'admin-editor'Used when loading an object in the Vue admin editor component.
'Editor'Used when loading an object in the generated Knockout CreateEdit views.
'<ModelName>ListGen'Used when loading a list of objects in the generated Knockout Table and Cards views. For example, PersonListGen

DtoIncludes & DtoExcludes

Main document: [DtoIncludes] & [DtoExcludes].

There are two C# attributes, DtoIncludes and DtoExcludes, that can be used to annotate your data model in order to customize what data gets put into the DTOs and ultimately serialized to JSON and sent out to the client.

When the database entries are returned to the client they will be trimmed based on the requested includes string and the values in DtoExcludes and DtoIncludes.

DANGER

These attributes are not security attributes - consumers of your application's API can set the includes string to any value when making a request.

Do not use them to keep certain data private - use the Security Attributes family of attributes for that.

It is important to note that the value of the includes string will match against these attributes on any of your models that appears in the object graph being mapped to DTOs - it is not limited only to the model type of the root object.

Important

DtoIncludes does not ensure that specific data will be loaded from the database. It only permits what is already loaded into the current EF DbContext to be returned from the API. See Data Sources to learn how to control what data gets loaded from the database.

Example Usage

Server code:

c#
public class Person
+{
+    // Don't include CreatedBy when editing - will be included for all other views
+    [DtoExcludes("Editor")]
+    public AppUser CreatedBy { get; set; }
+
+    // Only include the Person's Department when `includes == "details"` on the TypeScript ViewModel.
+    [DtoIncludes("details")]
+    public Department Department { get; set; }
+
+    // LastName will be included in all views
+    public string LastName { get; set; }
+}
+
+public class Department
+{
+    [DtoIncludes("details")]
+    public ICollection<Person> People { get; set; }
+}

Client code:

ts
import { PersonListViewModel } from '@/viewmodels.g'
+
+const personList = new PersonListViewModel();
+personList.$includes = "Editor";
+await personList.$load();
+// Objects in personList.$items will not contain CreatedBy nor Department objects.
+
+const personList2 = new PersonListViewModel();
+personList2.$includes = "details";
+await personList.$load();
+// Objects in personList2.items will be allowed to contain both CreatedBy and Department objects. 
+// Department will be allowed to include its other Person objects.

Properties

// Also settable via constructor parameter #1
+public string ContentViews { get; set; }

A comma-delimited list of values of includes on which to operate.

For DtoIncludes, this will be the values of includes for which this property will be allowed to be serialized and sent to the client.

For DtoExcludes, this will be the values of includes for which this property will not be serialized and sent to the client.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/ef-logo.svg b/ef-logo.svg new file mode 100644 index 000000000..c6fa67bbc --- /dev/null +++ b/ef-logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..72230b4569a868a0d1e98d6622fb8747fb453056 GIT binary patch literal 15086 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 literal 0 HcmV?d00001 diff --git a/hashmap.json b/hashmap.json new file mode 100644 index 000000000..4d64f5219 --- /dev/null +++ b/hashmap.json @@ -0,0 +1 @@ +{"stacks_disambiguation_view-model.md":"CJJYNLOF","modeling_model-components_attributes_typescript-partial.md":"4Crd20VW","stacks_disambiguation_list-view-model.md":"yRpM_Wq2","modeling_model-components_attributes_controller-action.md":"Ldygq9AT","modeling_model-components_attributes_coalesce.md":"Lkb6B2Rs","modeling_model-components_attributes_select-filter.md":"O79G0Zlu","introduction.md":"aI2TP5X5","stacks_agnostic_dtos.md":"8_xJeZ2y","index.md":"J5qzQ5SM","modeling_model-types_services.md":"CHVSzKDm","modeling_model-types_external-types.md":"qdzqqDbb","modeling_model-components_attributes_list-text.md":"6SOGN1hl","modeling_model-components_behaviors.md":"34ydZMzc","modeling_model-components_attributes_security-attribute.md":"jOlvg0wG","modeling_model-components_attributes_load-from-data-source.md":"mGs5QFTJ","stacks_vue_layers_metadata.md":"m2bw93Lp","stacks_ko_client_methods.md":"1ReCEXt-","stacks_vue_layers_models.md":"hU96gV_Y","modeling_model-components_attributes.md":"IUIXuOcX","modeling_model-components_properties.md":"gx6u8T0D","modeling_model-components_attributes_controller.md":"4MsqYLok","stacks_vue_layers_api-clients.md":"BwZLF0NI","stacks_vue_layers_viewmodels.md":"y7kFb0eu","modeling_model-components_methods.md":"_iwSjDHF","modeling_model-components_attributes_dto-includes-excludes.md":"gWiALlZf","stacks_vue_coalesce-vue-vuetify_components_c-table.md":"n25jKYu7","modeling_model-components_attributes_date-type.md":"IriIMwEk","stacks_vue_coalesce-vue-vuetify_components_c-select-string-value.md":"tV0v8rsN","stacks_ko_client_bindings.md":"uLy2BLbT","stacks_vue_coalesce-vue-vuetify_components_c-loader-status.md":"FwR65IYg","history.md":"ux281dRc","modeling_model-components_attributes_hidden.md":"MV6N1IoA","stacks_vue_getting-started.md":"2JJlpDJm","stacks_vue_coalesce-vue-vuetify_components_c-select-many-to-many.md":"kg745vjv","stacks_vue_coalesce-vue-vuetify_components_c-list-range-display.md":"ahES0O89","stacks_vue_coalesce-vue-vuetify_components_c-admin-display.md":"w8gdv3pP","concepts_includes.md":"va6Oh-E9","modeling_model-components_attributes_default-order-by.md":"2t7Z9gZk","modeling_model-components_attributes_inject.md":"wUXXw8WO","stacks_vue_coalesce-vue-vuetify_components_c-admin-audit-log-page.md":"GDNXyXjl","stacks_vue_coalesce-vue-vuetify_components_c-admin-editor-page.md":"SUSnocBA","modeling_model-components_attributes_internal-use.md":"J0q2AD4y","modeling_model-types_dtos.md":"SyTF2MTh","stacks_vue_coalesce-vue-vuetify_components_c-admin-table-page.md":"a48uAuy8","stacks_vue_coalesce-vue-vuetify_components_c-admin-table.md":"PTEjWSuO","stacks_vue_coalesce-vue-vuetify_components_c-display.md":"BR23Vc5-","stacks_vue_coalesce-vue-vuetify_components_c-list-page-size.md":"iQFFEkp9","stacks_vue_coalesce-vue-vuetify_components_c-list-page.md":"VoIz1Ye0","stacks_vue_coalesce-vue-vuetify_components_c-list-pagination.md":"dKBYttYP","stacks_vue_coalesce-vue-vuetify_components_c-admin-editor.md":"oQcY14k_","stacks_vue_coalesce-vue-vuetify_components_c-admin-method.md":"b9u2lyQx","stacks_vue_coalesce-vue-vuetify_components_c-admin-methods.md":"roY2qkqn","stacks_vue_coalesce-vue-vuetify_components_c-datetime-picker.md":"z5FrZ602","stacks_ko_client_external-view-model.md":"mJX84X3y","stacks_vue_coalesce-vue-vuetify_components_c-admin-table-toolbar.md":"hkVtE9hX","stacks_ko_client_list-view-model.md":"sYLWdQOg","stacks_vue_coalesce-vue-vuetify_components_c-input.md":"g15AvQ1z","stacks_ko_overview.md":"rAQfoqex","stacks_ko_client_view-model.md":"cA7v0DUf","concepts_include-tree.md":"tAg1rwYk","stacks_vue_overview.md":"PKO1LKIV","stacks_ko_getting-started.md":"KI5cX2XQ","stacks_ko_client_model-config.md":"t5pvBrNy","stacks_vue_coalesce-vue-vuetify_components_c-select-values.md":"mjZZaKzu","topics_coalesce-json.md":"U3jptpSC","stacks_vue_coalesce-vue-vuetify_components_c-list-filters.md":"3ww2oFHW","stacks_agnostic_generation.md":"--UgUCa6","stacks_vue_coalesce-vue-vuetify_overview.md":"SycRM1Rq","modeling_model-types_entities.md":"kyLYfGOY","topics_audit-logging.md":"m_cBN5t5","modeling_model-components_attributes_execute.md":"OmkAEPLi","stacks_agnostic_getting-started-modeling.md":"KbmYXIa-","stacks_vue_coalesce-vue-vuetify_components_c-select.md":"sYvwmnUu","modeling_model-components_attributes_client-validation.md":"1GRmSFWa","topics_security.md":"DU-P7c3D","stacks_disambiguation_external-view-model.md":"m3dMwNWm","modeling_model-components_attributes_restrict.md":"LYUDlSbo","modeling_model-components_attributes_create-controller.md":"R_UFOdqz","modeling_model-components_attributes_many-to-many.md":"66__Haw9","modeling_model-components_data-sources.md":"bsBPSQ6R","topics_startup.md":"rzuiSq70","modeling_model-components_attributes_search.md":"q7FurM4k","stacks_vue_vue2-to-vue3.md":"hLyUCPCI"} diff --git a/history.html b/history.html new file mode 100644 index 000000000..ecb498561 --- /dev/null +++ b/history.html @@ -0,0 +1,26 @@ + + + + + + Welcome to Coalesce's documentation! | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Welcome to Coalesce's documentation!

Coalesce is a framework based on ASP.NET Core that makes rapidly building awesome websites much easier. A project that would take a 3 months to complete now takes 1 month. We built this because we got tired of writing all the boiler plate code that is necessary to make amazing sites.

It does this by allowing developers to focus on the creative aspects of the solution. The more mundane parts are generated automatically. This means that you get to focus on data modeling, business logic and front-end development. Coalesce does the plumbing.

Here is a typical workflow

  1. Build an EF Core data model with business logic
  2. Coalesce generates controllers, TypeScript view models, API and view model documentation, and admin pages/examples
  3. Build an interactive and intuitive user experience
  4. Rinse and repeat

Core Features

  • Built on the latest Microsoft ASP.NET Core
  • Easy to learn
  • TypeScript from the ground up
  • Flexibility to use MVC patterns as required
  • Admin pages for all your models are build automatically and include features like searching, sorting, and paging
  • Robust documentation for the framework
  • Automatically generated documentation for the API layer and TypeScript view models
  • Feature rich TypeScript view models that can be easily extended
  • Many extension points for customizations
  • Abstraction that doesn't require you know how everything works
  • Security and data trimming by role is built in
  • Flexibility about which data to return to the client
  • Open source

Is Coalesce for Everyone and Every Project?

Coalesce was designed to create line-of-business applications. It provides a more customizable and maintainable alternative to off the shelf customizable products like SharePoint and Sales Force.

You should consider using Coalesce if your project:

  • Is small to medium size (1-200 classes)
  • Requires an interactive user experience
  • Has data entry requirements, especially forms, tables, etc.
  • Needs to get started quickly with functional prototypes that can become production software

Design Decisions and Limitations

Coalesce is specifically designed to meet the needs of web developers. However, there are lots of ways to do this. We have made a set of decisions which we believe makes for a great development experience

  • ASP.NET Core: there is no intent to back port this to an earlier version
  • EF Core for the object relational mapper
  • Currently uses the full framework because .NET Core doesn't supported the required functionality, yet
  • Knockout for client-side data binding
  • Business logic most easily lives in the model classes
  • Coalesce is designed for relational databases. This might change in the future, but not until we have a compelling use case.

How Does it Work?

After you create your classes and the EF data context, Coalesce uses this information to generate code. When the Coalesce CLI (command line interface) is run, the following things happen:

  1. The model is validated to ensure that all the Coalesce specific requirements are met. This includes things like ensuring that all classes have a primary key assigned, validating that linked child objects have a key to their parent, etc. If issues are found, generation stops and the errors are displayed with advise to fix the issues.
  2. The core files needed for Coalesce are copied to the target project. This includes TypeScript base classes, customizable templates, and other files for extension points. Each file is copied twice, once as a file that can be modified in the project and once as an original file. This ensures that if any changes are made by the user these files Coalesce will not overwrite the your changes.
  3. The API controllers are generated. One is generated for each object. This includes methods that get a list of items, get a specific item, save an item, etc.
  4. The TypeScript view models are created. There are two view models for each object. One is a list view model which allows for getting an displaying lists of a type of object. This includes full functionality to sort, filter, search, page, etc. Additionally, a view model that represents the individual object is also created. This has all the properties and methods of the server side object. This is basically a client-side proxy object for representing and manipulating the object on the server side. These objects seamlessly use the API controllers to interact with the server.
  5. Next, the View controller are created. One is create for each model class and provide a tabular view, a card view (for mobile), an editor, and documentation.
  6. Finally, the CSHTML views for the controller are created. These are the actual CSHTML for the above controllers. These not only provide administrative view and editing features, but also serve as an example of how to use the framework

General Guidance

Here are a few things we have found helpful when using Coalesce

  • Learn and embrace the Coalesce paradigm and work with it rather than trying to do things another way.
  • Following what we refer to as the 'well worn path' is very helpful. Try to stick to standard ways to do things rather than trying to use esoteric features.
  • Keep your models as consistent and straightforward as possible. Use relational modeling best practices.
  • Remember that public methods on your class models are added to the client side view models and this makes calling business logic from the client really easy.
  • Don't be afraid to fall back to building parts of your site using traditional methods. Coalesce isn't right for everything. But, honestly, we have only done this a few times, like 3.

The Story

Why Coalesce

In 2014 several developers from IntelliTect got together to talk about our craft. There were lots of different backgrounds, but recently we had all been writing web code in C#. We discussed things we enjoyed and things we dreaded. There was an underlying commitment to providing customers with great sites at a reasonable cost. However, those things often seemed at odds because of the complexity of web development.

The Problem

For example, writing AJAX drop down lists with type ahead takes quite a bit of plumbing. Layer onto this the need for view models that allow for validation and saving as the user moves from field to field. We absolutely want want to deliver visually pleasing sites with complex UI paradigms. However, all this excellence adds up: complex view models, complex APIs, data binding, ugh.

Then there is that sinking feeling when you have to add another class to the project knowing that you are going to need to create all this yet again and you consider taking short cuts. Will there really be more than about 20 items in this table, maybe we don't need paging. Inevitably, the customer asks for admin screens. We consider giving them SQL Server Management Console and then consider using the built in ASP.NET list and editor pages. Better sense wins out and we end up spending two weeks building slick admin pages with paging, searching, sorting, etc.

The Path to the Solution

That evening we starting talking about the things we loved to do:

  • Data modeling
  • Figuring out and writing business logic
  • Working with customers
  • Making cool user interfaces
  • Creating something new and awesome

We also lists things that we didn't enjoy

  • Writing the same controller again
  • Creating a view model for a class that is similar but different from another one in the project
  • Putting sorting and paging on every admin page
  • Basically doing anything that feels repetitive or boilerplate

Over the next few months we talked about this issue, but couldn't find the right abstraction. We talked about other solutions that solve parts of the problem and considered putting together something from several pieces. Nothing felt unified and we ended up with leaky abstractions. We needed some way to divide the problem so that we could build the fun stuff and have something generate the boring stuff. This solution needed to be robust enough to satisfy our customer's needs and also be of use to developers without their needing to know the inner workings of the system.

Our Solution

What if we could build the models and business logic and have a tool build everything except the UI? There are great tools like Entity Framework for modeling and good tooling for minimizing duplicate code in user interfaces. And so Coalesce was born, a tool that would bring together the backend and front end development. '

Coalesce takes Entity Framework Core models and builds controllers, TypeScript view models, and admin pages automatically. These are built in a general way so that they can be applied to many different scenarios. There will always be pages that need to be written by hand and we intentionally don't support many edge cases in order to keep things simple. There is nothing wrong with building something by hand.

How has it Worked?

We have been using Coalesce for many of our web projects with great success. Typically, a project is taking about 1/3 the time it was taking before once developers ramp up. The ramp up on Coalesce has typically been a couple of days. We realized that in order for Coalesce to be useful it need to be intuitive to use and easy to understand. We have intentionally used simple paradigms to minimize the learning curve. There are complex bits, but hopefully, those are well hidden and documented as needed.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 000000000..0661d2250 --- /dev/null +++ b/index.html @@ -0,0 +1,26 @@ + + + + + + Coalesce | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Coalesce

Accelerated Web App Development

ASP.NET Core • EF Core • Vue.js • TypeScript

Coalesce

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/intellitect-text-black.svg b/intellitect-text-black.svg new file mode 100644 index 000000000..f2236edae --- /dev/null +++ b/intellitect-text-black.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + diff --git a/intellitect-text-white.svg b/intellitect-text-white.svg new file mode 100644 index 000000000..07e2a13c3 --- /dev/null +++ b/intellitect-text-white.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + diff --git a/introduction.html b/introduction.html new file mode 100644 index 000000000..00f9f2df2 --- /dev/null +++ b/introduction.html @@ -0,0 +1,26 @@ + + + + + + Introduction | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Coalesce

Designed to help you quickly build amazing web applications, Coalesce is a rapid-development, code generation-based web application framework created by IntelliTect and built on top of:

C#, .NET, and ASP.NET Core are the backend foundation of all Coalesce applications.
Design your relational data model with Entity Framework. Coalesce will use that data model to generate an extensible, customizable CRUD API that will drive both your custom pages and the out-of-the-box admin pages.
Frontend code for Coalesce apps is generated for and written in TypeScript, enabling discovery of Coalesce features through Intellisense and providing confidence that your code won't break as your application grows.
Build an awesome frontend application with Vue.js. Coalesce will generate TypeScript ViewModels to facilitate rapid development of custom pages.
Vite is the development and build tooling for your frontend Vue code, enabling lightning-fast single-page application development. Coalesce integrates ASP.NET Core and Vite together, streamlining local development to require nothing more than a dotnet run or a single-click launch in your IDE.

What do I do?

You are responsible for the interesting parts of your application:

  • Data Model
  • Business Logic
  • External Integrations
  • Page Content
  • Site Design
  • Custom Scripting

What is done for me?

Coalesce builds the part of your application that are mundane and monotonous to build:

  • Client side TypeScript ViewModels that mirror your data model for both lists and individual objects. Utilize these to rapidly build out your application's various pages.
  • APIs to interact with your models via endpoints like List, Get, Save, and more.
  • Out-of-the-box Vue Components for common controls like dates, selecting objects via drop downs, enums, etc. Dropdowns support searching and paging automatically.
  • A complete set of admin pages are provided, allowing you to read, create, edit, and delete data straight away without writing any additional code.

Getting Started

To get started with Coalesce, check out Getting Started with Vue.

While Knockout.js is still supported by Coalesce, it is a deprecated option and not recommended for new projects. If you do still want to choose Knockout, click here.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes.html b/modeling/model-components/attributes.html new file mode 100644 index 000000000..3c6451cc4 --- /dev/null +++ b/modeling/model-components/attributes.html @@ -0,0 +1,26 @@ + + + + + + Attributes | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Attributes

Coalesce provides a number of C# attributes that can be used to decorate your model classes and their properties in order to customize behavior, appearance, security, and more. Coalesce also supports a number of annotations from System.ComponentModel.DataAnnotations.

Coalesce Attributes

Browse the list in the sidebar to learn about the attributes that Coalesce provides that can be used to decorate your models.

ComponentModel Attributes

Coalesce also supports a number of the built-in System.ComponentModel.DataAnnotations attributes and will use these to shape the generated code.

[Display]

The displayed name and description of a property, as well as the order in which it appears in generated views, can be set via the [Display] attribute. By default, properties will be displayed in the order in which they are defined in their class.

[DisplayName]

The displayed name of a property can also be set via the [DisplayName] attribute.

[Required]

Properties with [Required] will generate client validation and server validation rules.

[Range]

Properties with [Range] will generate client validation and server validation rules.

[MinLength]

Properties with [MinLength] will generate client validation and server validation rules.

[MaxLength]

Properties with [MaxLength] will generate client validation and server validation rules.

[DataType]

Some values of DataType when provided to DataTypeAttribute on a string property will alter the behavior of the Vue Components. See c-display and See c-display for details.

[ForeignKey]

Normally, Coalesce figures out which properties are foreign keys, but if you don't use standard EF naming conventions then you'll need to annotate with [ForeignKey] to help out both EF and Coalesce. See the Entity Framework Relationships documentation for more.

[InverseProperty]

Sometimes, Coalesce (and EF, too) can have trouble figuring out what the foreign key is supposed to be for a collection navigation property. See the Entity Framework Relationships documentation for details on how and why to use [InverseProperty].

[DatabaseGenerated]

Primary Keys with [DatabaseGenerated(DatabaseGeneratedOption.None)] will be settable on the client and will be appropriately handled by the Standard Behaviors on the server. Unsupported on the Knockout front-end stack.

[NotMapped]

Model properties that aren't mapped to the database should be marked with [NotMapped] so that Coalesce doesn't try to load them from the database when searching or carrying out the Default Loading Behavior.

[DefaultValue]

Properties with [DefaultValue] will receive the specified value when a new ViewModel is instantiated on the client. This enables scenarios like pre-filling a required property with a suggested value.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/client-validation.html b/modeling/model-components/attributes/client-validation.html new file mode 100644 index 000000000..ff2e70fd6 --- /dev/null +++ b/modeling/model-components/attributes/client-validation.html @@ -0,0 +1,56 @@ + + + + + + [ClientValidation] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[ClientValidation]

The [IntelliTect.Coalesce.DataAnnotations.ClientValidation] attribute is used to control the behavior of client-side model validation and to add additional client-only validation parameters. Database validation is available via standard System.ComponentModel.DataAnnotations annotations.

These propagate to the client as validations in TypeScript via generated Metadata and ViewModel rules (for Vue) or Knockout-Validation rules (for Knockout). For both stacks, any failing validation rules prevent saves from going to the server.

WARNING

This attribute controls client-side validation only. To perform server-side validation, create a custom Behaviors class for your types and/or place C# validation attributes on your models. Read More.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+
+    [ClientValidation(IsRequired = true, AllowSave = true)]
+    public string FirstName { get; set; }
+
+    [ClientValidation(IsRequired = true, AllowSave = false, MinLength = 1, MaxLength = 100)]
+    public string LastName { get; set; }
+}

Properties

Behavioral Properties

public bool AllowSave { get; set; } // Knockout Only

If set to true, any client validation errors on the property will not prevent saving on the client. This includes all client-side validation, including null-checking for required foreign keys and other validations that are implicit. This also includes other explicit validation from System.ComponentModel.DataAnnotations annotations.

Instead, validation errors will be treated only as warnings, and will be available through the warnings: KnockoutValidationErrors property on the TypeScript ViewModel.

Note

Use AllowSave = true to allow partially complete data to still be saved, protecting your user from data loss upon navigation while still hinting to them that they are not done filling out data.

public string ErrorMessage { get; set; }

Set an error message to be used if any client validations fail

Validation Rule Properties

In addition to the following properties, you also customize validation on a per-instance basis of the ViewModels using the Rules/Validation methods.

c#
public bool IsRequired { get; set; }
+public double MinValue { get; set; } = double.MaxValue;
+public double MaxValue { get; set; } = double.MinValue;
+public double MinLength { get; set; } = double.MaxValue;
+public double MaxLength { get; set; } = double.MinValue;
+public string Pattern { get; set; }
+public bool IsEmail { get; set; }
+public bool IsPhoneUs { get; set; }

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/coalesce.html b/modeling/model-components/attributes/coalesce.html new file mode 100644 index 000000000..a8952c42b --- /dev/null +++ b/modeling/model-components/attributes/coalesce.html @@ -0,0 +1,26 @@ + + + + + + [Coalesce] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[Coalesce]

Used to mark a type or member for generation by Coalesce.

Some types and members will be implicitly included in generation - for example, all types represented by a DbSet<T> on a DbContext that has a [Coalesce] attribute will automatically be included. Properties on these types will also be generated for unless explicitly excluded, since this is by far the most common usage scenario in Coalesce.

On the other hand, Methods on these types will not have endpoints generated unless they are explicitly annotated with [Coalesce] to avoid accidentally exposing methods that were perhaps not intended to be exposed.

The documentation pages for types and members that require/accept this attribute will state as such. An exhaustive list of all valid targets for [Coalesce] will not be found on this page.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/controller-action.html b/modeling/model-components/attributes/controller-action.html new file mode 100644 index 000000000..9be7e0b97 --- /dev/null +++ b/modeling/model-components/attributes/controller-action.html @@ -0,0 +1,53 @@ + + + + + + [ControllerAction] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[ControllerAction]

Specifies how a custom method is exposed via HTTP. Can be used to customize the HTTP method/verb for the method, as well as caching behavior.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    public string LastName { get; set; }
+
+    public string PictureHash { get; set; }
+
+    [Coalesce]
+    [ControllerAction(Method = HttpMethod.Get)]
+    public static long PersonCount(AppDbContext db, string lastNameStartsWith = "")
+    {
+        return db.People.Count(f => f.LastName.StartsWith(lastNameStartsWith));
+    }
+
+    [Coalesce]
+    [ControllerAction(HttpMethod.Get, VaryByProperty = nameof(PictureHash))]
+    public IFile GetPicture(AppDbContext db)
+    {
+        return new IntelliTect.Coalesce.Models.File(db.PersonPictures
+            .Where(x => x.PersonId == this.PersonId)
+            .Select(x => x.Content)
+        )
+        {
+            ContentType = "image/jpg",
+        };
+    }
+}

Properties

// Also settable via constructor parameter #1
+public HttpMethod Method { get; set; } = HttpMethod.Post;

The HTTP method to use on the generated API Controller.

Enum values are:

  • HttpMethod.Post Use the POST method.
  • HttpMethod.Get Use the GET method.
  • HttpMethod.Put Use the PUT method.
  • HttpMethod.Delete Use the DELETE method.
  • HttpMethod.Patch Use the PATCH method.

public string VaryByProperty { get; set; }

For HTTP GET model instance methods, if VaryByProperty is set to the name of a property on the parent model class, ETag headers based on the value of this property will be used to implement caching. If the client provides a matching If-None-Match Header with the request, the method will not be invoked and HTTP Status `304 Not Modified`` will be returned.

Additionally, if the VaryByProperty is set to a client-exposed property, the value of the property will be included in the query string when performing API calls to invoke the method. If the query string value matches the current value on the model, a long-term Cache-Control header will be set on the response, allowing the client to avoid making future invocations to the same method while the value of the VaryByProperty remains the same.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/controller.html b/modeling/model-components/attributes/controller.html new file mode 100644 index 000000000..f858990c3 --- /dev/null +++ b/modeling/model-components/attributes/controller.html @@ -0,0 +1,32 @@ + + + + + + [Controller] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[Controller]

Allows for control over the generated MVC Controllers.

Currently only controls over the API controllers are present, but additional properties may be added in the future.

This attribute may be placed on any type from which an API controller is generated, including Entity Models, Custom DTOs, and Services.

Example Usage

c#
[Controller(ApiRouted = false, ApiControllerSuffix = "Gen", ApiActionsProtected = true)]
+public class Person
+{
+    public int PersonId { get; set; }
+    
+    ...
+}

Properties

public bool ApiRouted { get; set; } = true;

Determines whether or not a [Route] annotation will be placed on the generated API controller. Set to false to prevent emission of the [Route] attribute.

Use cases include:

  • Defining your routes through IRouteBuilder in Startup.cs instead
  • Preventing API controllers from being exposed by default.
  • Routing to your own custom controller that inherits from the generated API controller in order to implement more granular or complex authorization logic.

public string ApiControllerName { get; set; } = null;

If set, will determine the name of the generated API controller.

Takes precedence over the value of ApiControllerSuffix.

public string ApiControllerSuffix { get; set; } = null;

If set, will be appended to the default name of the API controller generated for this model.

Will be overridden by the value of ApiControllerName if it is set.

public bool ApiActionsProtected { get; set; } = false;

If true, actions on the generated API controller will have an access modifier of protected instead of public.

In order to consume the generated API controller, you must inherit from the generated controller and override each desired generated action method via hiding (i.e. use public new ..., not public override ...).

Note

If you inherit from the generated API controllers and override their methods without setting ApiActionsProtected = true, all non-overridden actions from the generated controller will still be exposed as normal.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/create-controller.html b/modeling/model-components/attributes/create-controller.html new file mode 100644 index 000000000..60204cc99 --- /dev/null +++ b/modeling/model-components/attributes/create-controller.html @@ -0,0 +1,34 @@ + + + + + + [CreateController] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[CreateController]

By default an API and View controller are both created. This allows for suppressing the creation of either or both of these.

Example Usage

c#
[CreateController(view: false, api: true)]
+public class Person
+{
+    public int PersonId { get; set; }
+    
+    ...
+}

Properties

// Also settable via constructor parameter #1
+public bool WillCreateView { get; set; } = true

// Also settable via constructor parameter #2
+public bool WillCreateApi { get; set; } = true


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/date-type.html b/modeling/model-components/attributes/date-type.html new file mode 100644 index 000000000..ac9cefad1 --- /dev/null +++ b/modeling/model-components/attributes/date-type.html @@ -0,0 +1,33 @@ + + + + + + [DateType] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[DateType]

Specifies whether a DateTime type will have a date and a time, or only a date.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+
+    [DateType(DateTypeAttribute.DateTypes.DateOnly)]
+    public DateTimeOffset? BirthDate { get; set; }
+}

Properties

// Also settable via constructor parameter #1
+public DateTypes DateType { get; set; } = DateTypes.DateTime; 

The type of date the property represents.

Enum values are:

  • DateTypeAttribute.DateTypes.DateTime Subject is both a date and time.
  • DateTypeAttribute.DateTypes.DateOnly Subject is only a date with no significant time component.

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/default-order-by.html b/modeling/model-components/attributes/default-order-by.html new file mode 100644 index 000000000..1153a678c --- /dev/null +++ b/modeling/model-components/attributes/default-order-by.html @@ -0,0 +1,50 @@ + + + + + + [DefaultOrderBy] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[DefaultOrderBy]

Allows setting of the default manner in which the data returned to the client will be sorted. Multiple fields can be used to sort an object by specifying an index.

This affects the sort order both when requesting a list of the model itself, as well as when the model appears as a child collection off of a navigation property of another object.

In the first case (a list of the model itself), this can be overridden by setting the orderBy or orderByDescending property on the TypeScript ListViewModel - see TypeScript List ViewModels.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    
+    public int DepartmentId { get; set; }
+
+    [DefaultOrderBy(FieldOrder = 0, FieldName = nameof(Department.Order))]
+    public Department Department { get; set; }
+    
+    [DefaultOrderBy(FieldOrder = 1)]
+    public string LastName { get; set; }
+}
c#
public class Person
+{
+    public int PersonId { get; set; }
+    
+    public int DepartmentId { get; set; }
+
+    [DefaultOrderBy(FieldOrder = 0, FieldName = nameof(Department.Order))]
+    public Department Department { get; set; }
+    
+    [DefaultOrderBy(FieldOrder = 1)]
+    public string LastName { get; set; }
+}

Properties

// Also settable via constructor parameter #1
+public int FieldOrder { get; set; } = 0; 

Specify the index of this field when sorting by multiple fields.

Lower-valued properties will be used first; higher-valued properties will be used as a tiebreaker (i.e. .ThenBy(...)).

// Also settable via constructor parameter #2
+public OrderByDirections OrderByDirection { get; set; } = OrderByDirections.Ascending;

Specify the direction of the ordering for the property.

Enum values are:

  • DefaultOrderByAttribute.OrderByDirections.Ascending
  • DefaultOrderByAttribute.OrderByDirections.Descending

public string FieldName { get; set; }

When using the DefaultOrderByAttribute on an object property, specifies the field on the object to use for sorting. See the first example above.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/dto-includes-excludes.html b/modeling/model-components/attributes/dto-includes-excludes.html new file mode 100644 index 000000000..ccf02902e --- /dev/null +++ b/modeling/model-components/attributes/dto-includes-excludes.html @@ -0,0 +1,66 @@ + + + + + + [DtoIncludes] & [DtoExcludes] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[DtoIncludes] & [DtoExcludes]

Allows for easily controlling what data gets set to the client. When requesting data from the generated client-side list view models, you can specify an includes property on the ViewModel or ListViewModel.

For more information about the includes string, see Includes String.

When the database entries are returned to the client they will be trimmed based on the requested includes string and the values in DtoExcludes and DtoIncludes.

DANGER

These attributes are not security attributes - consumers of your application's API can set the includes string to any value when making a request.

Do not use them to keep certain data private - use the Security Attributes family of attributes for that.

It is important to note that the value of the includes string will match against these attributes on any of your models that appears in the object graph being mapped to DTOs - it is not limited only to the model type of the root object.

Important

DtoIncludes does not ensure that specific data will be loaded from the database. It only permits what is already loaded into the current EF DbContext to be returned from the API. See Data Sources to learn how to control what data gets loaded from the database.

Example Usage

Server code:

c#
public class Person
+{
+    // Don't include CreatedBy when editing - will be included for all other views
+    [DtoExcludes("Editor")]
+    public AppUser CreatedBy { get; set; }
+
+    // Only include the Person's Department when `includes == "details"` on the TypeScript ViewModel.
+    [DtoIncludes("details")]
+    public Department Department { get; set; }
+
+    // LastName will be included in all views
+    public string LastName { get; set; }
+}
+
+public class Department
+{
+    [DtoIncludes("details")]
+    public ICollection<Person> People { get; set; }
+}

Client code:

ts
import { PersonListViewModel } from '@/viewmodels.g'
+
+const personList = new PersonListViewModel();
+personList.$includes = "Editor";
+await personList.$load();
+// Objects in personList.$items will not contain CreatedBy nor Department objects.
+
+const personList2 = new PersonListViewModel();
+personList2.$includes = "details";
+await personList.$load();
+// Objects in personList2.items will be allowed to contain both CreatedBy and Department objects. 
+// Department will be allowed to include its other Person objects.

Properties

// Also settable via constructor parameter #1
+public string ContentViews { get; set; }

A comma-delimited list of values of includes on which to operate.

For DtoIncludes, this will be the values of includes for which this property will be allowed to be serialized and sent to the client.

For DtoExcludes, this will be the values of includes for which this property will not be serialized and sent to the client.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/execute.html b/modeling/model-components/attributes/execute.html new file mode 100644 index 000000000..8116a4acc --- /dev/null +++ b/modeling/model-components/attributes/execute.html @@ -0,0 +1,36 @@ + + + + + + [Execute] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[Execute]

Controls permissions for executing of a static or instance method through the API.

For other security controls, see Security Attributes.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    
+    [Coalesce, Execute(Roles = "Payroll,HR")]
+    public void GiveRaise(int centsPerHour) {
+        ...
+    }
+
+    ...
+}

Properties

public string Roles { get; set; }

A comma-separated list of roles which are allowed to execute the method.

public SecurityPermissionLevels PermissionLevel { get; set; } = SecurityPermissionLevels.AllowAuthorized;

The level of access to allow for the action for the method.

Enum values are:

  • SecurityPermissionLevels.AllowAll Allow all users to perform the action for the attribute, including users who are not authenticated at all.
  • SecurityPermissionLevels.AllowAuthorized Allow only users who are members of the roles specified on the attribute to perform the action. If no roles are specified on the attribute, then all authenticated users are allowed (no anonymous access).
  • SecurityPermissionLevels.DenyAll Deny the action to all users, regardless of authentication status or authorization level. If DenyAll is used, no API endpoint for the action will be generated.

public bool AutoClear { get; set; }

If true, the method's arguments will be cleared after a successful invocation on admin pages.

public bool? ValidateAttributes { get; set; }

If non-null, overrides the value of CoalesceOptions.ValidateAttributesForMethods when determining whether to perform automatic server-side validation of the method's parameters.

If validation is performed, the method's parameters will be validated by the server and the method invocation prevented if errors are found.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/hidden.html b/modeling/model-components/attributes/hidden.html new file mode 100644 index 000000000..01367ae0b --- /dev/null +++ b/modeling/model-components/attributes/hidden.html @@ -0,0 +1,33 @@ + + + + + + [Hidden] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[Hidden]

Mark an property as hidden from the edit, List or All areas.

DANGER

This attribute is not a security attribute - it only affects the rendering of the admin pages. It has no impact on data visibility in the API.

Do not use it to keep certain data private - use the Security Attributes family of attributes for that.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+
+    [Hidden(HiddenAttribute.Areas.All)]
+    public int? IncomeLevelId { get; set; }
+}

Properties

// Also settable via constructor parameter #1
+public Areas Area { get; set; } = Areas.All;

The areas in which the property should be hidden.

Enum values are:

  • HiddenAttribute.Areas.None Hide from no generated views. Primary and Foreign keys are hidden by default - setting this value explicitly can override this default behavior.
  • HiddenAttribute.Areas.All Hide from all generated views
  • HiddenAttribute.Areas.List Hide from generated list views only (Knockout Table/Cards, Vue c-admin-table)
  • HiddenAttribute.Areas.Edit Hide from generated editor only (Knockout CreateEdit, Vue c-admin-editor)

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/inject.html b/modeling/model-components/attributes/inject.html new file mode 100644 index 000000000..feb9f1697 --- /dev/null +++ b/modeling/model-components/attributes/inject.html @@ -0,0 +1,37 @@ + + + + + + [Inject] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[Inject]

Used to mark a Method parameter for dependency injection from the application's IServiceProvider.

See Methods for more.

This gets translated to a Microsoft.AspNetCore.Mvc.FromServicesAttribute in the generated API controller's action.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    public string FirstName { get; set; }
+    public string LastName { get; set; }
+
+    public string GetFullName([Inject] ILogger<Person> logger)
+    {
+        logger.LogInformation("Person " + PersonId + "'s full name was requested");
+        return FirstName + " " + LastName";
+    }
+}

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/internal-use.html b/modeling/model-components/attributes/internal-use.html new file mode 100644 index 000000000..e79959cde --- /dev/null +++ b/modeling/model-components/attributes/internal-use.html @@ -0,0 +1,45 @@ + + + + + + [InternalUse] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[InternalUse]

Used to mark a type, property or method for internal use. Internal Use members are:

  • Not exposed via the API.
  • Not present in the generated TypeScript view models.
  • Not present nor accounted for in the generated C# DTOs.
  • Not present in the generated editor or list views.

Effectively, an Internal Use member is invisible to Coalesce. This attribute can be considered a Security Attribute.

Note that this only needs to be used on members that are public. Non-public members (including internal) are always invisible to Coalesce.

Example Usage

In this example, Color is the property exposed to the API, but ColorHex is the property that maps to the database that stores the value. A helper method also exists for the color generation, but needs no attribute to be hidden since methods must be explicitly exposed with [Coalesce].

If no color is saved in the database (the user hasn't picked a color), one is deterministically created.

c#
public class ApplicationUser
+{
+    public int ApplicationUserId { get; set; }
+
+    [InternalUse]
+    public string ColorHex { get; set; }
+
+    [NotMapped]
+    public string Color
+    {
+        get => ColorHex ?? GenerateColor(ApplicationUserId).ToRGBHexString();
+        set => ColorHex = value;
+    }
+
+    public static HSLColor GenerateColor(int? seed = null)
+    {
+        var random = seed.HasValue ? new Random(seed.Value) : new Random();
+        return new HSLColor(random.NextDouble(), random.Next(40, 100) / 100d, random.Next(25, 65) / 100d);
+    }
+}

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/list-text.html b/modeling/model-components/attributes/list-text.html new file mode 100644 index 000000000..384a2bc3c --- /dev/null +++ b/modeling/model-components/attributes/list-text.html @@ -0,0 +1,37 @@ + + + + + + [ListText] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[ListText]

When a textual representation of an object needs to be displayed in the UI, this attribute controls which property will be used. Examples include dropdowns and cells in admin UI tables.

If this attribute is not used, and a property named Name exists on the model, that property will be used. Otherwise, the primary key will be used.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+
+    public string FirstName { get; set; }
+
+    public string LastName { get; set; }
+
+    [ListText]
+    [NotMapped]
+    public string Name => FirstName + " " + LastName
+}

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/load-from-data-source.html b/modeling/model-components/attributes/load-from-data-source.html new file mode 100644 index 000000000..ce5f7c2c1 --- /dev/null +++ b/modeling/model-components/attributes/load-from-data-source.html @@ -0,0 +1,37 @@ + + + + + + [LoadFromDataSource] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[LoadFromDataSource]

Specifies that the targeted model instance method should load the instance it is called on from the specified data source when invoked from an API endpoint. If not defined, the model's default data source is used.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    public string FirstName { get; set; }
+
+    [Coalesce, LoadFromDataSource(typeof(WithoutCases))]
+    public void ChangeSpacesToDashesInName()
+    {
+        FirstName = FirstName.Replace(" ", "-");
+    }
+}

Properties

// ONLY settable via constructor parameter #1
+public Type DataSourceType { get; }

The name of the Data Source to load the instance object from.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/many-to-many.html b/modeling/model-components/attributes/many-to-many.html new file mode 100644 index 000000000..6074ba9e2 --- /dev/null +++ b/modeling/model-components/attributes/many-to-many.html @@ -0,0 +1,35 @@ + + + + + + [ManyToMany] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[ManyToMany]

Used to specify a Many to Many relationship. Because EF core does not support automatic intermediate mapping tables, this field is used to allow for direct reference of the many-to-many collections from the ViewModel.

The named specified in the attribute will be used as the name of a collection of the objects on the other side of the relationship in the generated TypeScript ViewModels.

Example Usage

c#
public class Person
+{
+    public int PersonId { get; set; }
+    public string FirstName { get; set; }
+    public string LastName { get; set; }
+
+    [ManyToMany("Appointments")]
+    public ICollection<PersonAppointment> PersonAppointments { get; set; }
+}

Properties

// ONLY settable via constructor parameter #1
+public string CollectionName { get; }

The name of the collection that will contain the set of objects on the other side of the many-to-many relationship.

public string FarNavigationProperty { get; set; }

The name of the navigation property on the middle entity that points at the far side of the many-to-many relationship. Use this to resolve ambiguities when the middle table of the many-to-many relationship has more than two reference navigation properties on it.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/restrict.html b/modeling/model-components/attributes/restrict.html new file mode 100644 index 000000000..4b8f6b916 --- /dev/null +++ b/modeling/model-components/attributes/restrict.html @@ -0,0 +1,48 @@ + + + + + + [Restrict] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[Restrict]

In addition to role-based property restrictions, you can also define property restrictions that can execute custom code for each model instance if your logic require more nuanced decisions than can be made with roles.

c#
using IntelliTect.Coalesce.DataAnnotations;
+public class Employee 
+{
+  public int Id { get; set; }
+
+  [Read]
+  public string UserId { get; set; }
+
+  [Restrict<SalaryRestriction>]
+  public decimal Salary { get; set; }
+}
+
+public class SalaryRestriction(MyUserService userService) : IPropertyRestriction<Employee>
+{
+  public bool UserCanRead(IMappingContext context, string propertyName, Employee model)
+    => context.User.GetUserId() == model.UserId || userService.IsPayroll(context.User);
+
+  public bool UserCanWrite(IMappingContext context, string propertyName, Employee model, object incomingValue)
+    => userService.IsPayroll(context.User);
+
+  public bool UserCanFilter(IMappingContext context, string propertyName)
+    => userService.IsPayroll(context.User);
+}

Restriction classes support dependency injection, so you can inject any supplemental services needed to make a determination.

The UserCanRead method controls whether values of the restricted property will be mapped from model instances to the generated DTO. Similarly, UserCanWrite controls whether the property can be mapped back to the model instance from the generated DTO.

The UserCanFilter method has a default implementation that returns false, but can be implemented if there is an appropriate, instance-agnostic way to determine if a user can sort, search, or filter values of that property.

Multiple different restrictions can be placed on a single property; all of them must succeed for the operation to be permitted. Restrictions also stack on top of role attribute restrictions ([Read] and [Edit]).

A non-generic variant of IPropertyRestriction also exists for restrictions that might be reused across multiple model types.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/search.html b/modeling/model-components/attributes/search.html new file mode 100644 index 000000000..25c8ea44c --- /dev/null +++ b/modeling/model-components/attributes/search.html @@ -0,0 +1,43 @@ + + + + + + [Search] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/security-attribute.html b/modeling/model-components/attributes/security-attribute.html new file mode 100644 index 000000000..8d13b651a --- /dev/null +++ b/modeling/model-components/attributes/security-attribute.html @@ -0,0 +1,54 @@ + + + + + + Security Attributes | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Security Attributes

Coalesce provides a collection of attributes which can provide class-level (and property-level, where appropriate) security controls over the generated API.

TIP

This page provides API-level documentation for a specific set of attributes. For a complete overview of all the security-focused techniques that can be used in a Coalesce application, see the Security page.

Class vs. Property Security

There are important differences between class-level security and property-level security, beyond the usage of the attributes themselves. In general, class-level security is implemented in the generated API Controllers as [Authorize] attributes on the generated actions. Property security attributes are implemented in the Generated C# DTOs.

Implementations

[Read]

Controls permissions for reading of objects and properties through the API.

For property-level security only, if a [Read] attribute is present without an [Edit] attribute, the property is read-only.

Additionally, you can set NoAutoInclude = true the [Read] attribute to suppress the Default Loading Behavior.

Example Usage

c#
[Read(Roles = "Management", PermissionLevel = SecurityPermissionLevels.AllowAuthorized)]
+public class Employee
+{
+    public int EmployeeId { get; set; }
+
+    [Read("Payroll")]
+    public string LastFourSsn { get; set; }
+    
+    ...
+}

[Edit]

Controls permissions for editing of objects and properties through the API.

For property-level security only, if a [Read] attribute is present, one of its roles must be fulfilled in addition to the roles specified (if any) for the [Edit] attribute.

Example Usage

c#
[Edit(Roles = "Management,Payroll", PermissionLevel = SecurityPermissionLevels.AllowAuthorized)]
+public class Employee
+{
+    public int EmployeeId { get; set; }
+
+    [Read("Payroll,HumanResources"), Edit("Payroll")]
+    public string LastFourSsn { get; set; }
+    
+    ...
+}

[Create]

Controls permissions for creation of an object of the targeted type through the API.

Example Usage

c#
[Create(Roles = "HumanResources", PermissionLevel = SecurityPermissionLevels.AllowAuthorized)]
+public class Employee
+{
+    ...
+}

[Delete]

Controls permissions for deletion of an object of the targeted type through the API.

Example Usage

c#
[Delete(Roles = "HumanResources,Management", PermissionLevel = SecurityPermissionLevels.AllowAuthorized)]
+public class Employee
+{
+    ...
+}

[Execute]

A separate attribute for controlling method execution exists. Its documentation may be found on the [Execute] page.

[Restrict]

For property security, [Read] and [Edit] can be used to apply role-based security. If you need logic more complicated than checking for the presence of a role, [Restrict] offers the ability to write custom code to control the read and/or write permissions of a property.

Attribute Properties

// Also settable via constructor parameter #1
+public string Roles { get; set; }

A comma-delimited list of roles that are authorized to take perform the action represented by the attribute. If the current user belongs to any of the listed roles, the action will be allowed.

The string set for this property will be outputted as an [Authorize(Roles="RolesString")] attribute on generated API controller actions.

// Also settable via constructor parameter #2
+public SecurityPermissionLevels PermissionLevel { get; set; }

The level of access to allow for the action for class-level security only. Has no effect for property-level security.

Enum values are:

  • SecurityPermissionLevels.AllowAll Allow all users to perform the action for the attribute, including users who are not authenticated at all.
  • SecurityPermissionLevels.AllowAuthorized Allow only users who are members of the roles specified on the attribute to perform the action. If no roles are specified on the attribute, then all authenticated users are allowed (no anonymous access).
  • SecurityPermissionLevels.DenyAll Deny the action to all users, regardless of authentication status or authorization level. If DenyAll is used, no API endpoint for the action will be generated.

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/select-filter.html b/modeling/model-components/attributes/select-filter.html new file mode 100644 index 000000000..4afb7fd0b --- /dev/null +++ b/modeling/model-components/attributes/select-filter.html @@ -0,0 +1,44 @@ + + + + + + [SelectFilter] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[SelectFilter]

WARNING

This attribute only affects the generated Knockout HTML views - it does not enforce any relational rules in your data.

This attribute also currently has no effect against the Vue stack.

Specify a property to restrict dropdown menus by. Values presented will be only those where the value of the foreign property matches the value of the local property.

The local property name defaults to the same value of the foreign property.

Additionally, in place of a LocalPropertyName to check against, you may instead specify a static value using StaticPropertyValue to filter by a constant.

Example Usage

In this example, a dropdown for EmployeeRank created using @Knockout.SelectForObject in cshtml files will only present possible values of EmployeeRank which are valid for the EmployeeType of the Employee.

c#
public class Employee
+{
+    public int EmployeeId { get; set; }
+    public int EmployeeTypeId { get; set; }
+    public EmployeeType EmployeeType { get; set; }
+    public int EmployeeRankId { get; set; }
+
+    [SelectFilter(ForeignPropertyName = nameof(EmployeeRank.EmployeeTypeId), LocalPropertyName = nameof(Employee.EmployeeTypeId))]
+    public EmployeeRank EmployeeRank { get; set; }
+}
+
+public class EmployeeRank
+{
+    public int EmployeeRankId { get; set; }
+    public int EmployeeTypeId { get; set; }
+    public EmployeeType EmployeeType { get; set; }
+}
razor
<div>
+    @(Knockout.SelectForObject<Models.Employee>(e => e.EmployeeRank))
+</div>

Properties

public string ForeignPropertyName { get; set; }

The name of the property on the foreign object to filter against.

public string LocalPropertyName { get; set; }

The name of another property belonging to the class in which this attribute is used. The results of select lists will be filtered to match this value.

Defaults to the value of ForeignPropertyName if not set.

public string LocalPropertyObjectName { get; set; }

If specified, the LocalPropertyName will be resolved from the property by this name that resides on the local object.

This allows for querying against properties that are one level away from the current object.

public string StaticPropertyValue { get; set; }

A constant value that the foreign property will be filtered against. This string must be parsable into the foreign property's type to have any effect. If this is set, LocalPropertyName will be ignored.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/attributes/typescript-partial.html b/modeling/model-components/attributes/typescript-partial.html new file mode 100644 index 000000000..3c83733eb --- /dev/null +++ b/modeling/model-components/attributes/typescript-partial.html @@ -0,0 +1,32 @@ + + + + + + [TypeScriptPartial] | Coalesce + + + + + + + + + + + + + + + +
Skip to content

[TypeScriptPartial]

Note

This attribute only applies to the Knockout front-end stack. It is not applicable to the Vue stack.

If defined on a model, a typescript file will be generated in ./Scripts/Partials if one does not already exist. This 'Partial' TypeScript file contains a class which inherits from the generated TypeScript ViewModel. The partial class has the same name as the generated ViewModel would normally have, and the generated ViewModel is renamed to "<ClassName>Partial".

This behavior allows you to extend the behavior of the generated TypeScript view models with your own properties and methods for defining more advanced behavior on the client. One of the most common use cases of this is to define additional Knockout ComputedObservable properties for information that is only useful in the browser - for example, computing a css class based on data in the object.

Example Usage

c#
[TypeScriptPartial]
+public class Employee
+{
+    public int EmployeeId { get; set; }
+
+    ...
+}

Properties

public string BaseClassName { get; set; }

If set, overrides the name of the generated ViewModel which becomes the base class for the generated 'Partial' TypeScript file.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/behaviors.html b/modeling/model-components/behaviors.html new file mode 100644 index 000000000..a4bee2473 --- /dev/null +++ b/modeling/model-components/behaviors.html @@ -0,0 +1,95 @@ + + + + + + Behaviors | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Behaviors

In a CRUD system, creating, updating, and deleting are considered especially different from reading. In Coalesce, the dedicated classes that perform these operations are derivatives of a special interface known as the IBehaviors<T>. These are their stories.


Coalesce separates out the parts of your API that read your data from the parts that mutate it. The read portion is performed by Data Sources, and the mutations are performed by behaviors. Like data sources, there exists a standard set of behaviors that Coalesce provides out-of-the-box that cover the most common use cases for creating, updating, and deleting objects in your data model.

Also like data sources, these functions can be easily overridden on a per-model basis, allowing complete control over the ways in which your data is mutated by the APIs that Coalesce generates. However, unlike data sources which can have as many implementations per model as you like, you can only have one set of behaviors.

Defining Behaviors

By default, each of your models that Coalesce exposes will utilize the standard behaviors (IntelliTect.Coalesce.StandardBehaviors<T, TContext>) for the out-of-the-box API endpoints that Coalesce provides. These behaviors provide a set of create, update, and delete methods for an EF Core DbContext, as well as a plethora of virtual methods that make the StandardBehaviors a great base class for your custom implementations. Unlike data sources which require an annotation to override the Coalesce-provided standard class, the simple presence of an explicitly declared set of behaviors will suppress the standard behaviors.

Note

When you define a set of custom behaviors, take note that these are only used by the standard set of API endpoints that Coalesce always provides. They will not be used to handle any mutations in any Methods you write for your models.

To create your own behaviors, you simply need to define a class that implements IntelliTect.Coalesce.IBehaviors<T>. To expose your behaviors to Coalesce, either place it as a nested class of the type T that your behaviors are for, or annotate it with the [Coalesce] attribute. Of course, the easiest way to create behaviors that doesn't require you to re-engineer a great deal of logic would be to inherit from IntelliTect.Coalesce.StandardBehaviors<T, TContext>, and then override only the parts that you need.

c#
public class Case
+{
+    public int CaseId { get; set; }
+    public int OwnerId { get; set; }
+    public bool IsDeleted { get; set; }
+    ...
+}
+
+[Coalesce]
+public class CaseBehaviors : StandardBehaviors<Case, AppDbContext>
+{
+    public CaseBehaviors(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override ItemResult BeforeSave(SaveKind kind, Case oldItem, Case item)
+    {
+        // Allow admins to bypass all validation.
+        if (User.IsInRole("Admin")) return true;
+
+        if (kind == SaveKind.Update && oldItem.OwnerId != item.OwnerId)
+            return "The owner of a case may not be changed";
+
+        // This is a new item, OR its an existing item and the owner isn't being modified.
+        if (item.CreatedById != User.GetUserId())
+            return "You are not the owner of this item.";
+
+        return true;
+    }
+
+    public override ItemResult BeforeDelete(Case item) 
+        => User.IsInRole("Manager") ? true : "Unauthorized";
+
+    public override Task ExecuteDeleteAsync(Case item)
+    {
+        // Soft delete the item.
+        item.IsDeleted = true;
+        return Db.SaveChangesAsync();
+    }
+}

Dependency Injection

All behaviors are instantiated using dependency injection and your application's IServiceProvider. As a result, you can add whatever constructor parameters you desire to your behaviors as long as a value for them can be resolved from your application's services. The single parameter to the StandardBehaviors is resolved in this way - the CrudContext<TContext> contains the common set of objects most commonly used, including the DbContext and the ClaimsPrincipal representing the current user.

Standard Behaviors

The standard behaviors, IntelliTect.Coalesce.StandardBehaviors<T> and its EntityFramework-supporting sibling IntelliTect.Coalesce.StandardBehaviors<T, TContext>, contain a significant number of properties and methods that can be utilized and/or overridden at your leisure.

Properties

CrudContext<TContext> Context

The object passed to the constructor that contains the set of objects needed by the standard behaviors, and those that are most likely to be used in custom implementations.

TContext Db

An instance of the db context that contains a DbSet<T> for the entity handled by the behaviors

ClaimsPrincipal User

The user making the current request.

IDataSource<T> OverrideFetchForUpdateDataSource

A data source that, if set, will override the data source that is used to retrieve the target of an update operation from the database. The incoming values will then be set on this retrieved object. Null by default; override by setting a value in the constructor.

IDataSource<T> OverridePostSaveResultDataSource

A data source that, if set, will override the data source that is used to retrieve a newly-created or just-updated object from the database after a save. The retrieved object will be returned to the client. Null by default; override by setting a value in the constructor.

IDataSource<T> OverrideFetchForDeleteDataSource

A data source that, if set, will override the data source that is used to retrieve the target of an delete operation from the database. The retrieved object will then be deleted. Null by default; override by setting a value in the constructor.

IDataSource<T> OverridePostDeleteResultDataSource

A data source that, if set, will override the data source that is used to retrieve the target of an delete operation from the database after it has been deleted. If an object is able to be retrieved from this data source, it will be sent back to the client. This allows soft-deleted items to be returned to the client when the user is able to see them. Null by default; override by setting a value in the constructor.

Method Overview

The standard behaviors implementation contains many different methods which can be overridden in your derived class to control functionality.

These methods often call one another, so overriding one method may cause some other method to no longer be called. The hierarchy of method calls, ignoring any logic or conditions contained within, is as follows:

SaveAsync
+    DetermineSaveKindAsync
+    GetDbSet
+    ValidateDto
+    MapIncomingDto
+    BeforeSaveAsync
+        BeforeSave
+    ExecuteSaveAsync
+    AfterSave
+
+DeleteAsync
+    BeforeDeleteAsync
+        BeforeDelete
+    ExecuteDeleteAsync
+        GetDbSet
+    AfterDelete

Method Details

All of the methods outlined above can be overridden. A description of each of the methods is as follows:

Task<ItemResult<TDto?>> SaveAsync<TDto>(TDto incomingDto, IDataSource<T> dataSource, IDataSourceParameters parameters)

Save the given item. This is the main entry point for saving, and takes a DTO as a parameter. This method is responsible for performing mapping to your EF models and ultimately saving to your database. If it is required that you access properties from the incoming DTO in this method, a set of extension methods GetValue and GetObject are available on the DTO for accessing properties that are mapped 1:1 with your EF models.

Task<(SaveKind Kind, object? IncomingKey)> DetermineSaveKindAsync<TDto>(TDto incomingDto, IDataSource<T> dataSource, IDataSourceParameters parameters)

Given the incoming DTO on which Save has been called, examine its properties to determine if the operation is meant to be a create or an update operation. Return this distinction along with the key that was used to make the distinction.

This method is called outside of the standard data source by the base API controller to perform role-based security on saves at the controller level.

DbSet<T> GetDbSet()

Returns a DbSet<T> that items can be added to (creates) or remove from (deletes).

ItemResult ValidateDto(SaveKind kind, IClassDto<T> dto)

Provides a chance to validate the properties of the DTO object itself, as opposed to doing validation in BeforeSave of the properties of the model after the DTO has been mapped to the model. This also where attribute-based validation is performed.

To perform custom validation in this method (uncommon), there are a number of extension methods on IClassDto<T> that can be used to access the value of the properties of Generated C# DTOs. For behaviors on Custom DTOs where the DTO type is known, simply cast to the correct type.

T MapIncomingDto<TDto>(SaveKind kind, T? item, TDto dto, IDataSourceParameters parameters)

Map the properties of the incoming DTO to the model that will be saved to the database. For a SaveKind.Create, this will call the MapToNew method on the DTO and a new instance must be returned (item will be null). For a SaveKind.Update, this will call the MapTo method on the DTO, and the incoming item must be returned. If more precise control is needed, extension methods on IClassDto<T> or casting to a known type can be used to get specific values. If all else fails, the DTO can be reflected upon.

Task<ItemResult> BeforeSaveAsync(SaveKind kind, T? oldItem, T item);
+ItemResult BeforeSave(SaveKind kind, T? oldItem, T item)

Provides an easy way for derived classes to intercept a save attempt and either reject it by returning an unsuccessful result, or approve it by returning success. The incoming item can also be modified at will in this method to override changes that the client made as desired.

ItemResult AfterSave(SaveKind kind, T? oldItem, ref T item, ref IncludeTree? includeTree)

Provides an easy way for derived classes to perform actions after a save operation has been completed. Failure results returned here will present an error to the client, but will not prevent modifications to the database since changes have already been saved at this point. This method can optionally modify or replace the item that is sent back to the client after a save by setting ref T item to another object or to null. Setting ref IncludeTree includeTree will override the Include Tree used to shape the response object.

WARNING

Setting ref T item to null will prevent the new object from being returned - be aware that this can be harmful in create scenarios since it prevents the client from receiving the primary key of the newly created item. If autoSave is enabled on the client, this could cause a large number of duplicate objects to be created in the database, since each subsequent save by the client will be treated as a create when the incoming object lacks a primary key.

Task<ItemResult<TDto?>> DeleteAsync<TDto>(object id, IDataSource<T> dataSource, IDataSourceParameters parameters)

Deletes the given item.

Task<ItemResult> BeforeDeleteAsync(T item);
+ItemResult BeforeDelete(T item)

Provides an easy way to intercept a delete request and potentially reject it (by returning a non-success ItemResult).

Task ExecuteDeleteAsync(T item)

Performs the delete action against the database. The implementation of this method removes the item from its corresponding DbSet<T>, and then calls Db.SaveChangesAsync().

Overriding this allows for changing this row-deletion implementation to something else, like setting of a soft delete flag, or copying the data into another archival table before deleting.

void AfterDelete(ref T item, ref IncludeTree? includeTree)

Allows for performing any sort of cleanup actions after a delete has completed. If the item was still able to be retrieved from the database after the delete operation completed, this method allows lets you modify or replace the item that is sent back to the client by setting ref T item to another object or to null. Setting ref IncludeTree includeTree will override the Include Tree used to shape the response object.

Globally Replacing the Standard Behaviors

You can, of course, create a custom base behaviors class that all your custom implementations inherit from. But, what if you want to override the standard behaviors across your entire application, so that StandardBehaviors<,> will never be instantiated? You can do that too!

Simply create a class that implements IEntityFrameworkBehaviors<,> (the StandardBehaviors<,> already does - feel free to inherit from it), then register it at application startup like so:

c#
public class MyBehaviors<T, TContext> : StandardBehaviors<T, TContext>
+    where T : class
+    where TContext : DbContext
+{
+    public MyBehaviors(CrudContext<TContext> context) : base(context)
+    {
+    }
+
+    ...
+}
c#
public void ConfigureServices(IServiceCollection services)
+{
+    services.AddCoalesce(b =>
+    {
+        b.AddContext<AppDbContext>();
+        b.UseDefaultBehaviors(typeof(MyBehaviors<,>));
+    });

Your custom behaviors class must have the same generic type parameters - <T, TContext>. Otherwise, the Microsoft.Extensions.DependencyInjection service provider won't know how to inject it.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/data-sources.html b/modeling/model-components/data-sources.html new file mode 100644 index 000000000..402846460 --- /dev/null +++ b/modeling/model-components/data-sources.html @@ -0,0 +1,136 @@ + + + + + + Data Sources | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Data Sources

In Coalesce, all data that is retrieved from your database through the generated controllers is done so by a data source. These data sources control what data gets loaded and how it gets loaded. By default, there is a single generic data source that serves all data for your models in a generic way that fits many of the most common use cases - the Standard Data Source.

In addition to this standard data source, Coalesce allows you to create custom data sources that provide complete control over the way data is loaded and serialized for transfer to a requesting client. These data sources are defined on a per-model basis, and you can have as many of them as you like for each model.

Defining Data Sources

By default, each of your models that Coalesce exposes will expose the standard data source (IntelliTect.Coalesce.StandardDataSource<T, TContext>). This data source provides all the standard functionality one would expect - paging, sorting, searching, filtering, and so on. Each of these component pieces is implemented in one or more virtual methods, making the StandardDataSource a great place to start from when implementing your own data source. To suppress this behavior of always exposing the raw StandardDataSource, create your own custom data source and annotate it with [DefaultDataSource].

To implement your own custom data source, you simply need to define a class that implements IntelliTect.Coalesce.IDataSource<T>. To expose your data source to Coalesce, either place it as a nested class of the type T that you data source serves, or annotate it with the [Coalesce] attribute. Of course, the easiest way to create a data source that doesn't require you to re-engineer a great deal of logic would be to inherit from IntelliTect.Coalesce.StandardDataSource<T, TContext>, and then override only the parts that you need.

c#
public class Person
+{
+    [DefaultDataSource]
+    public class IncludeFamily : StandardDataSource<Person, AppDbContext>
+    {
+        public IncludeFamily(CrudContext<AppDbContext> context) : base(context) { }
+
+        public override IQueryable<Person> GetQuery(IDataSourceParameters parameters) 
+            => Db.People
+            .Where(f => User.IsInRole("Admin") || f.CreatedById == User.GetUserId())
+            .Include(f => f.Parents).ThenInclude(s => s.Parents)
+            .Include(f => f.Cousins).ThenInclude(s => s.Parents);
+    }
+}
+
+[Coalesce]
+public class NamesStartingWithA : StandardDataSource<Person, AppDbContext>
+{
+    public NamesStartingWithA(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override IQueryable<Person> GetQuery(IDataSourceParameters parameters) 
+        => Db.People.Include(f => f.Siblings).Where(f => f.FirstName.StartsWith("A"));
+}

The structure of the IQueryable built by the various methods of StandardDataSource is used to shape and trim the structure of the DTO as it is serialized and sent out to the client. One may also override method IncludeTree GetIncludeTree(IQueryable<Person> query, IDataSourceParameters parameters) to control this explicitly. See Include Tree for more information on how this works.

WARNING

If you create a custom data source that has custom logic for securing your data, be aware that the default implementation of StandardDataSource (or your custom default implementation - see below) is still exposed unless you annotate one of your custom data sources with [DefaultDataSource]. Doing so will replace the default data source with the annotated class for your type T.

Dependency Injection

All data sources are instantiated using dependency injection and your application's IServiceProvider. As a result, you can add whatever constructor parameters you desire to your data sources as long as a value for them can be resolved from your application's services. The single parameter to the StandardDataSource is resolved in this way - the CrudContext<TContext> contains the common set of objects most commonly used, including the DbContext and the ClaimsPrincipal representing the current user.

Consuming Data Sources

The ViewModels and ListViewModels each have a property called $dataSource. This property accepts an instance of a DataSource class generated in the Model Layer.

ts
import { Person } from '@/models.g'
+import { PersonViewModel, PersonListViewModel } from '@/viewmodels.g'
+
+var viewModel = new PersonViewModel();
+viewModel.$dataSource = new Person.DataSources.IncludeFamily();
+viewModel.$load(1);
+
+var list = new PersonListViewModel();
+list.$dataSource = new Person.DataSources.NamesStartingWith();
+list.$load(1);

Standard Parameters

All methods on IDataSource<T> take a parameter that contains all the client-specified parameters for things paging, searching, sorting, and filtering information. Almost all virtual methods on StandardDataSource are also passed the relevant set of parameters.

Custom Parameters

On any data source that you create, you may add additional properties annotated with [Coalesce] that will then be exposed as parameters to the client. These property parameters are currently restricted to primitives (numeric types, strings) and dates (DateTime, DateTimeOffset). Property parameter primitives may be expanded to allow for more types in the future.

c#
[Coalesce]
+public class NamesStartingWith : StandardDataSource<Person, AppDbContext>
+{
+    public NamesStartingWith(CrudContext<AppDbContext> context) : base(context) { }
+
+    [Coalesce]
+    public string StartsWith { get; set; }
+
+    public override IQueryable<Person> GetQuery(IDataSourceParameters parameters) 
+        => Db.People.Include(f => f.Siblings)
+        .Where(f => string.IsNullOrWhitespace(StartsWith) ? true : f.FirstName.StartsWith(StartsWith));
+}

List Auto-loading

You can setup TypeScript List ViewModels to automatically reload from the server when data source parameters change:

To automatically reload a ListViewModel when data source parameters change, simply use the list's $useAutoLoad or $startAutoLoad function:

ts
import { Person } from '@/models.g';
+import { PersonListViewModel } from '@/viewmodels.g';
+
+const list = new PersonListViewModel;
+const dataSource = list.$dataSource = new Person.DataSources.NamesStartingWith
+list.$useAutoLoad(); // When using options API, use $startAutoLoad(this)
+
+// Trigger a reload:
+dataSource.startsWith = "Jo";

Standard Data Source

The standard data sources, IntelliTect.Coalesce.StandardDataSource<T> and its EntityFramework-supporting sibling IntelliTect.Coalesce.StandardDataSource<T, TContext>, contain a significant number of properties and methods that can be utilized and/or overridden at your leisure.

Default Loading Behavior

When an object or list of objects is requested, the default behavior of the the StandardDataSource is to load all of the immediate relationships of the object (parent objects and child collections), as well as the far side of many-to-many relationships. This is performed in StandardDataSource.GetQuery(), so in order to suppress this behavior in a custom data source, don't build you query off of base.GetQuery(), but instead start directly from the DbSet for your entity when building your custom query.

Clients can suppress this per-request by setting includes = "none" on your TypeScript ViewModel or ListViewModel, but note this is not a security mechanism and should only be used to reduce payload size or improve response time.

On the server, you can suppress this behavior by placing [Read(NoAutoInclude = true)] on either an entire class (affecting all navigation properties of that type), or on specific navigation properties. When placed on a entity class that holds sensitive data, this can help ensure you don't accidentally leak records due to forgetting to customize the data sources of the types whose navigation properties reference your sensitive entity.

Properties

The following properties are available for use on the StandardDataSource any any derived instances.

CrudContext<TContext> Context

The object passed to the constructor that contains the set of objects needed by the standard data source, and those that are most likely to be used in custom implementations.

TContext Db

An instance of the DbContext that contains a DbSet<T> for the entity served by the data source.

ClaimsPrincipal User

The user making the current request.

int MaxSearchTerms

The max number of search terms to process when interpreting a search term word-by-word. Override by setting a value in the constructor.

int DefaultPageSize

The page size to use if none is specified by the client. Override by setting a value in the constructor.

int MaxPageSize

The maximum page size that will be served. By default, client-specified page sizes will be clamped to this value. Override by setting a value in the constructor.

Method Overview

The standard data source contains 19 different methods which can be overridden in your derived class to control its behavior.

These methods often call one another, so overriding one method may cause some other method to no longer be called. The hierarchy of method calls, ignoring any logic or conditions contained within, is as follows:

GetMappedItemAsync
+    GetItemAsync
+        GetQueryAsync
+            GetQuery
+        GetIncludeTree
+    TransformResults
+
+GetMappedListAsync
+    GetListAsync
+        GetQueryAsync
+            GetQuery
+        ApplyListFiltering
+            ApplyListPropertyFilters
+                ApplyListPropertyFilter
+            ApplyListSearchTerm
+        GetListTotalCountAsync
+        ApplyListSorting
+            ApplyListClientSpecifiedSorting
+            ApplyListDefaultSorting
+        ApplyListPaging
+        GetIncludeTree
+    TrimListFields
+    TransformResults
+
+GetCountAsync
+    GetQueryAsync
+        GetQuery
+    ApplyListFiltering
+        ApplyListPropertyFilters
+            ApplyListPropertyFilter
+        ApplyListSearchTerm
+    GetListTotalCountAsync

Method Details

All of the methods outlined above can be overridden. A description of each of the non-interface inner methods is as follows:

IQueryable<T> GetQuery(IDataSourceParameters parameters);
+Task<IQueryable<T>> GetQueryAsync(IDataSourceParameters parameters);

The method is the one that you will most commonly be override in order to implement custom query logic. The default implementation of GetQueryAsync simply calls GetQuery - be aware of this in cases of complex overrides/inheritance. From this method, you could:

  • Specify additional query filtering such as row-level security or soft-delete logic. Or, restrict the data source entirely for users or whole roles by returning an empty query.
  • Include additional data using EF's .Include() and .ThenInclude().
  • Add additional edges to the serialized object graph using Coalesce's .IncludedSeparately() and .ThenIncluded().

Note

When GetQuery is overridden, the Default Loading Behavior is overridden as well. To restore this behavior, use the IQueryable<T>.IncludeChildren() extension method to build your query.

IncludeTree? GetIncludeTree(IQueryable<T> query, IDataSourceParameters parameters)

Allows for explicitly specifying the Include Tree that will be used when serializing results obtained from this data source into DTOs. By default, the query that is build up through all the other methods in the data source will be used to build the include tree.

bool CanEvalQueryAsynchronously(IQueryable<T> query)

Called by other methods in the standard data source to determine whether or not EF Core async methods will be used to evaluate queries. This may be globally disabled when bugs like https://github.com/dotnet/SqlClient/issues/593 are present in EF Core.

IQueryable<T> ApplyListFiltering(IQueryable<T> query, IFilterParameters parameters)

A simple wrapper that calls ApplyListPropertyFilters and ApplyListSearchTerm.

IQueryable<T> ApplyListPropertyFilters(IQueryable<T> query, IFilterParameters parameters)

For each value in parameters.Filter, invoke ApplyListPropertyFilter to apply a filter to the query.

IQueryable<T> ApplyListPropertyFilter(IQueryable<T> query, PropertyViewModel prop, string value)

Given a property and a client-provided string value, perform some filtering on that property.

  • Dates with a time component will be matched exactly.
  • Dates with no time component will match any dates that fell on that day.
  • Strings will match exactly unless an asterisk is found, in which case they will be matched with string.StartsWith.
  • Enums will match by string or numeric value. Multiple comma-delimited values will create a filter that will match on any of the provided values.
  • Numeric values will match exactly. Multiple comma-delimited values will create a filter that will match on any of the provided values.

IQueryable<T> ApplyListSearchTerm(IQueryable<T> query, IFilterParameters parameters)

Applies filters to the query based on the specified search term. See [Search] for a detailed look at how searching works in Coalesce.

IQueryable<T> ApplyListSorting(IQueryable<T> query, IListParameters parameters)

If any client-specified sort orders are present, invokes ApplyListClientSpecifiedSorting. Otherwise, invokes ApplyListDefaultSorting.

IQueryable<T> ApplyListClientSpecifiedSorting(IQueryable<T> query, IListParameters parameters)

Applies sorting to the query based on sort orders specified by the client. If the client specified "none" as the sort field, no sorting will take place.

IQueryable<T> ApplyListDefaultSorting(IQueryable<T> query)

Applies default sorting behavior to the query, including behavior defined with use of [DefaultOrderBy] in C# POCOs, as well as fallback sorting to "Name" or primary key properties.

IQueryable<T> ApplyListPaging(IQueryable<T> query, IListParameters parameters, int? totalCount, out int page, out int pageSize)

Applies paging to the query based on incoming parameters. Provides the actual page and pageSize that were used as out parameters.

Task<int> GetListTotalCountAsync(IQueryable<T> query, IFilterParameters parameters)

Simple wrapper around invoking .Count() on a query.

void TransformResults(IReadOnlyList<T> results, IDataSourceParameters parameters);
+Task TransformResultsAsync(IReadOnlyList<T> results, IDataSourceParameters parameters);

Allows for transformation of a result set after the query has been evaluated. This will be called for both lists of items and for single items. This can be used for populating non-mapped properties on a model, or conditionally loading navigation properties using logic that depends upon the contents of each loaded record.

This method is only called immediately before mapping to a DTO; it does not affect operations that don't involve mapping to a DTO - e.g. when loading the target of a /save operation or when loading the invocation target of an instance method.

See the Security page for an example on how to use TransformResults to apply filtered includes.

Do not use TransformResults to modify any database-mapped scalar properties, since such changes could be inadvertently persisted to the database.

IList<TDto> TrimListFields<TDto>(IList<TDto> mappedResult, IListParameters parameters)

Performs trimming of the fields of the result set based on the parameters given to the data source. Can be overridden to forcibly disable this, override the behavior to always trim specific fields, or any other functionality desired.

Globally Replacing the Standard Data Source

You can, of course, create a custom base data source that all your custom implementations inherit from. But, what if you want to override the standard data source across your entire application, so that StandardDataSource<,> will never be instantiated? You can do that too!

Simply create a class that implements IEntityFrameworkDataSource<,> (the StandardDataSource<,> already does - feel free to inherit from it), then register it at application startup like so:

c#
public class MyDataSource<T, TContext> : StandardDataSource<T, TContext>
+    where T : class
+    where TContext : DbContext
+{
+    public MyDataSource(CrudContext<TContext> context) : base(context)
+    {
+    }
+
+    ...
+}
c#
public void ConfigureServices(IServiceCollection services)
+{
+    services.AddCoalesce(b =>
+    {
+        b.AddContext<AppDbContext>();
+        b.UseDefaultDataSource(typeof(MyDataSource<,>));
+    });
+}

Your custom data source must have the same generic type parameters - <T, TContext>. Otherwise, the Microsoft.Extensions.DependencyInjection service provider won't know how to inject it.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/methods.html b/modeling/model-components/methods.html new file mode 100644 index 000000000..865513c7d --- /dev/null +++ b/modeling/model-components/methods.html @@ -0,0 +1,159 @@ + + + + + + Methods | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Methods

Any public methods annotated with the [Coalesce] attribute that are placed on your model classes will have API endpoints and Typescript generated by Coalesce. Both instance methods and static methods are supported. Additionally, any instance methods on Services will also have API endpoints and TypeScript generated.

These custom methods allow you to implement any custom server-side functionality in your Coalesce application that falls outside of the standard CRUD functions that are generated for your entities.

Declaring Methods

Instance Methods

Instance Methods can be declared on your Entity classes. For example:

c#
public class User
+{
+    public int UserId { get; set; }
+
+    public string Email { get; set; }
+
+    [Coalesce]
+    public async Task<ItemResult> SendMessage(
+        [Inject] SmtpClient client,
+        ClaimsPrincipal sender,
+        string message
+    ) {
+        if (string.IsNullOrWhitespace(Email)) return "Recipient has no email";
+        if (string.IsNullOrWhitespace(message)) return "Message is required";
+
+        await client.SendMailAsync(new MailMessage(  
+            from: sender.GetEmailAddress(),
+            to: Email,
+            subject: "Message from MyApp",  
+            body: message
+        ));
+        return true;
+    }
+}

When an instance method is invoked, the target model instance will be loaded using the data source specified by an attribute [LoadFromDataSource(typeof(MyDataSource))] if present. Otherwise, the model instance will be loaded using the default data source for the model's type. If you have a Custom Data Source annotated with [DefaultDataSource], that data source will be used. Otherwise, the Standard Data Source will be used. The consequence of this is that a user cannot call a method on an instance of entity that they're not allowed to see or load.

Instance methods are generated onto the TypeScript ViewModels.

When should I use Instance Methods?

Instance methods, as opposed to static or service methods, are a good fit when implementing an action that directly acts on or depends upon a specific instance of one of your entity types. One of their biggest benefits is the automatic row-level security from data sources as described above.

Static Methods

Static Methods can be declared on your Entity classes. For example:

c#
public class Person 
+{
+    public int PersonId { get; set; }
+
+    public string FirstName { get; set; }
+
+    [Coalesce]
+    public static ICollection<string> NamesStartingWith(
+        AppDbContext db,
+        string characters 
+    ) {
+        return db.People
+            .Select(p => p.FirstName)
+            .Where(f => f.StartsWith(characters))
+            .ToList();
+    }
+}

Static methods are generated onto the TypeScript ListViewModels. All of the same members that are generated for instance methods are also generated for static methods.

When should I use Static Methods?

Static methods are a good fit for actions that don't operate on a specific instance of an entity type, but whose functionality is still closely coupled with a specific, concrete entity type.

For example, imagine you have a File entity class. You could make a static method on that class that accepts a file as a parameter. This method would persist that file to storage and then save a new entity to the database. You would then disable Create on that entity, since the default /save endpoint cannot accept file uploads.

Or, imagine an Invoice class. You might make a static method that returns a summary of sales information for a given time range. Since this summarization would be performing aggregate functions against your Invoice entities and is therefore tightly coupled to Invoices, a static method would be suitable.

Service Methods

Service methods can be declared on a Coalesce Service class:

c#
[Coalesce, Service]
+public class MyService 
+{
+  [Coalesce]
+  public string MyServiceMethod() => "Hello, World!";
+}

Or, they can be declared via a Coalesce Service interface that has an implementation registered with dependency injection:

c#
[Coalesce, Service]
+public interface IMyService 
+{
+  string MyServiceMethod() => "Hello, World!";
+}

When declaring service methods by interface, a [Coalesce] attribute on each method is not needed - the entire interface is exposed by Coalesce.

When should I use Service Methods?

Services are a catch-all feature and can be used for almost any conceivable purpose in Coalesce to implement custom functionality that needs to be invoked by your front-end app.

However, there are some reasons why you might not want to use a service:

  • If the method logically operates on a single entity instance, and/or if using an instance method would let you utilize the row-level security already implemented by one of your data sources to authorize who can invoke the method.
  • If the service would only have one or two methods and would logically make sense as a static or instance method. In other words, if adding a new service class would be detrimental to the organization of your codebase and create "file sprawl".

On the other hand, services have some benefits that instance and static methods cannot provide:

  • Coalesce Services can be declared with an interface, rather than a concrete type, allowing for their implementation to be substituted more easily. For example, a service providing an external integration that you want to mock or stub during automated testing and/or local development.

Parameters

The following parameters can be added to your methods:

TypeDescription

Primitives, Dates, and other Scalars

Most common built-in primitive and scalar data types (numerics, strings, booleans, enums, DateTime, DateTimeOffset), and their nullable variants, are accepted as parameters to be passed from the client to the method call.

Entity Models

When invoking the method on the client, the object's properties will only be serialized one level deep. If an entity model parameter has additional child object properties, they will not be included in the invocation of the method - only the object's primitive & date properties will be deserialized from the client.

External Types

Unlike entity model parameters, external type parameters will be serialized and sent by the client to an arbitrarily deep level, excluding any entity model properties that may be nested inside an external type.

Files

Methods can accept file uploads by using a parameter of type IntelliTect.Coalesce.Models.IFile (or any derived type, like IntelliTect.Coalesce.Models.File).

ICollection<T>, IEnumerable<T>

Collections of any of the above valid parameter types above are also valid parameter types.

DbContext

If the method has a parameter assignable to Microsoft.EntityFrameworkCore.DbContext, then the parameter will be implicitly [Inject]ed.

ClaimsPrincipal

If the method has a parameter of type ClaimsPrincipal, the value of HttpContext.User will be passed to the parameter.

[Inject]

If a parameter is marked with the [Inject] attribute, it will be injected from the application's IServiceProvider.

out IncludeTree

Deprecated. If you need to return an Include Tree to shape the serialization of the method's return value, you should use an ItemResult<T> return value and populate the IncludeTree property on the ItemResult object.

Return Values

You can return virtually anything from these methods:

TypeDescription

Primitives, Dates, and other Scalars

Most common built-in primitive and scalar data types (numerics, strings, booleans, enums, DateTime, DateTimeOffset), and their nullable variants, may be returned from methods.

Entity Models

Any of the types of your models may be returned.

External Types

Any External Types you define may also be returned from a method.

When returning custom types from methods, be careful of the types of their properties. Coalesce will recursively discover and generate code for all public properties of your External Types. If you accidentally include a type that you do not own, these generated types could get out of hand extremely quickly.

Mark any properties you don't want generated with the [InternalUse] attribute, or give them a non-public access modifier. Whenever possible, don't return types that you don't own or control.

ICollection<T>, IEnumerable<T>

Collections of any of the above valid return types above are also valid return types. IEnumerables are useful for generator functions using yield. ICollection is highly suggested over IEnumerable whenever appropriate, though.

IQueryable<T>

Queryables of the valid return types above are valid return types. The query will be evaluated, and Coalesce will attempt to pull an Include Tree from the queryable to shape the response.

When Include Tree functionality is needed to shape the response but an IQueryable<> return type is not feasible, an ItemResult return value with an IncludeTree set on it will do the trick as well.

Files

Methods can return file downloads using type IntelliTect.Coalesce.Models.IFile (or any derived type, like IntelliTect.Coalesce.Models.File).

Please see the File Downloads section below for more details

ItemResult<T>, ItemResult, ListResult<T>

An IntelliTect.Coalesce.Models.ItemResult<T> of any of the valid return types above, including collections, is valid, as well as its non-generic variant ItemResult, and its list variant ListResult<T>.

Use an ItemResult whenever you might need to signal failure and return an error message from a custom method. The WasSuccessful and Message properties on the result object will be sent along to the client to indicate success or failure of the method. The type T will be mapped to the appropriate DTO object before being serialized as normal.

An Include Tree can be set on the object's IncludeTree parameter to shape the serialization of the method's returned value.

Security

You can implement role-based security on a method by placing the [Execute] on the method. Placing this attribute on the method with no roles specified will simply require that the calling user be authenticated.

Security for instance methods is also controlled by the data source that loads the instance - if the data source can't provide an instance of the requested model, the method won't be executed.

See the Security page to read more about custom method security, as well as all other security mechanisms in Coalesce.

Generated TypeScript

See API Callers and ViewModel Layer for details on the code that is generated for your custom methods.

Note

Any Task-returning methods with "Async" as a suffix to the C# method's name will have the "Async" suffix stripped from the generated Typescript.

Method Annotations

Methods can be annotated with attributes to control API exposure and TypeScript generation. The following attributes are available for model methods. General annotations can be found on the Attributes page.

[Coalesce]

The [Coalesce] attribute causes the method to be exposed via a generated API controller. This is not needed for methods defined on an interface marked with [Service] - Coalesce assumes that all methods on the interface are intended to be exposed. If this is not desired, create a new, more restricted interface with only the desired methods to be exposed.

[ControllerAction(Method = HttpMethod, VaryByProperty = string)]

The [ControllerAction] attribute controls how this method is exposed via HTTP. Can be used to customize the HTTP method/verb for the method, as well as caching behavior.

[Execute(string roles)]

The [Execute] attribute specifies which roles can execute this method from the generated API controller. Additional security restrictions that cannot be implemented with roles should be enforced with custom code in the method's implementation.

[LoadFromDataSource(Type dataSourceType)]

The [LoadFromDataSource] attribute specifies that the targeted model instance method should load the instance it is called on from the specified data source when invoked from an API endpoint. If not defined, the model's default data source is used.

File Downloads

Coalesce supports exposing file downloads via custom methods. Simply return a IntelliTect.Coalesce.Models.IFile (or any derived type, like IntelliTect.Coalesce.Models.File), or an ItemResult<> of such.

Consuming file downloads

There are a few conveniences for easily consuming downloaded files from your custom pages.

The API Callers have a property url. This can be provided directly to your HTML template, with the browser invoking the endpoint automatically.

ts
import { PersonViewModel } from '@/viewmodels.g'
+
+var viewModel = new PersonViewModel();
+viewModel.$load(1);
html
<img :src="downloadPicture.url">

Alternatively, the API Callers for file-returning methods have a method getResultObjectUrl(vue). If the method was invoked programmatically (i.e. via caller(), caller.invoke(), or caller.invokeWithArgs()), this method returns an Object URL that can be set as the src of an image or video HTML tag.

ts
import { PersonViewModel } from '@/viewmodels.g'
+
+var viewModel = new PersonViewModel();
+await viewModel.$load(1);
+await viewModel.downloadPicture();
html
<img :src="downloadPicture.getResultObjectUrl()">

Database-stored Files

When storing large byte[] objects in your EF models, it is important that these are never loaded unless necessary. Loading these can cause significant garbage collector churn, or even bring your app to a halt. To achieve this with EF, you can either utilize Table Splitting, or you can use an entire dedicated table that only contains a primary key and the binary content, and nothing else.

WARNING

Storing large binary objects in relational databases comes with significant drawbacks. For large-volume cloud solutions, it is much more costly than dedicated cloud-native file storage like Azure Storage or S3. Also of note is that the larger a database is, the more difficult its backup process becomes.

For files that are stored in your database, Coalesce supports a pattern that allows the file to be streamed directly to the HTTP response without needing to allocate a chunk of memory for the whole file at once. Simply pass an EF IQueryable<byte[]> to the constructor of IntelliTect.Coalesce.Models.File. This implementation, however, is specific to the underlying EF database provider. Currently, only SQL Server and SQLite are supported. Please open a Github issue to request support for other providers. An example of this mechanism is included in the DownloadAttachment method in the code sample below.

The following is an example of utilizing Table Splitting for database-stored files. Generally speaking, metadata about the file should be stored on the "main" entity, and only the bytes of the content should be split into a separate entity.

c#
public class AppDbContext : DbContext
+{
+    public DbSet<Case> Cases { get; set; }
+
+    protected override void OnModelCreating(ModelBuilder modelBuilder)
+    {
+        modelBuilder
+            .Entity<Case>()
+            .ToTable("Cases")
+            .HasOne(c => c.AttachmentContent)
+            .WithOne()
+            .HasForeignKey<CaseAttachmentContent>(c => c.CaseId);
+        modelBuilder
+            .Entity<CaseAttachmentContent>()
+            .ToTable("Cases")
+            .HasKey(d => d.CaseId);
+    }
+}
+
+public class Case
+{
+    public int CaseId { get; set; }
+
+    [Read]
+    public string AttachmentName { get; set; }
+
+    [Read]
+    public long AttachmentSize { get; set; }
+
+    [Read]
+    public string AttachmentType { get; set; }
+
+    [Read, MaxLength(32)] // Adjust max length based on chosen hash algorithm.
+    public byte[] AttachmentHash { get; set; } // Could also be a base64 string if so desired.
+
+    [InternalUse]
+    public CaseAttachmentContent AttachmentContent { get; set; } = new();
+
+    [Coalesce]
+    public async Task UploadAttachment(AppDbContext db, IFile file)
+    {
+        if (file.Content == null) return;
+
+        var content = new byte[file.Length];
+        await file.Content.ReadAsync(content.AsMemory());
+
+        AttachmentContent = new () { CaseId = CaseId, Content = content };
+        AttachmentName = file.Name;
+        AttachmentSize = file.Length;
+        AttachmentType = file.ContentType;
+        AttachmentHash = SHA256.HashData(content);
+    }
+
+    [Coalesce]
+    [ControllerAction(HttpMethod.Get, VaryByProperty = nameof(AttachmentHash))]
+    public IFile DownloadAttachment(AppDbContext db)
+    {
+        return new IntelliTect.Coalesce.Models.File(db.Cases
+            .Where(c => c.CaseId == this.CaseId)
+            .Select(c => c.AttachmentContent.Content)
+        )
+        {
+            Name = AttachmentName,
+            ContentType = AttachmentType,
+        };
+    }
+}
+
+public class CaseAttachmentContent
+{
+    public int CaseId { get; set; }
+
+    [Required]
+    public byte[] Content { get; set; }
+}

Other File Storage

For any other storage mechanism, implementations are similar to the database storage approach above. However, instead of table splitting or using a whole separate table, the file contents are simply stored elsewhere. Continue storing metadata about the file on the primary entity, and implement upload/download methods as desired that wrap the storage provider.

For downloads, prefer directly providing the underlying Stream to the IFile versus wrapping a byte[] in a MemoryStream. This will reduce server memory usage and garbage collector churn.

For cloud storage providers where complex security logic is not needed, consider having clients consume the URL of the cloud resource directly rather than passing the file content through your own server.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-components/properties.html b/modeling/model-components/properties.html new file mode 100644 index 000000000..426c9e5c0 --- /dev/null +++ b/modeling/model-components/properties.html @@ -0,0 +1,26 @@ + + + + + + Properties | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Properties

Models in a Coalesce application are just EF Core POCOs. The properties defined on your models should fit within the constraints of EF Core.

Coalesce currently has a few more restrictions than what EF Core allows, but hopefully over time some of these restrictions can be relaxed as Coalesce grows in capability.

Property Varieties

The following kinds of properties may be declared on your models.

Primary Key

To work with Coalesce, your model must have a single property for a primary key. By convention, this property should be named the same as your model class with Id appended to that name, but you can also annotate a property with [Key] or name it exactly "Id" to denote it as the primary key.

Foreign Keys & Reference Navigation Properties

While a foreign key may be declared on your model using only the EF OnModuleBuilding method to specify its purpose, Coalesce won't know what the property is a key for. Therefore, foreign key properties should always be accompanied by a reference navigation property, and vice versa.

In cases where the foreign key is not named after the navigation property with "Id" appended, the [ForeignKeyAttribute] may be used on either the key or the navigation property to denote the other property of the pair, in accordance with the recommendations set forth by EF Core's Modeling Guidelines.

Collection Navigation Properties

Collection navigation properties can be used in a straightforward manner. In the event where the inverse property on the other side of the relationship cannot be determined, [InversePropertyAttribute] will need to be used. EF Core provides documentation on how to use this attribute. Errors will be displayed at generation time if an inverse property cannot be determined without the attribute. We recommend recommended that you declare the type of collection navigation properties as ICollection<T>.

Non-mapped POCOs

Properties of a type that are not on your DbContext will also have corresponding properties generated on the TypeScript ViewModels typed as TypeScript External ViewModels, and the values of such properties will be sent with the object to the client when requested. Properties of this type will also be sent back to the server by the client when they are encountered (currently supported by the Vue Stack only).

See External Types for more information.

Primitives, Scalars, & Dates

Most common built-in primitive and scalar data types (numerics, strings, booleans, enums, DateTime, DateTimeOffset), and their nullable variants, are all supported as model properties.

Getter-only Properties

Any property that only has a getter will also have a corresponding property generated in the TypeScript ViewModels, but won't be sent back to the server during any save actions.

If such a property is defined as an auto-property, the [NotMapped] attribute should be used to prevent EF Core from attempting to map such a property to your database.

Init-only Properties

Properties on Entity Models that use an init accessor rather than a set accessor will be implicitly treated as required, and can also only have a value provided when the entity is created for the first time. Any values provided during save actions for init-only properties when updating an existing entity will be ignored.

Property Customization

For any of the kinds of properties outlined above, the following customizations can be applied:

Attributes

Coalesce provides a number of Attributes, and supports a number of other .NET attributes, that allow for further customization of your model.

Security

Property values received by the server from the client will be ignored if rejected by any property-level Security. This security is implemented in the Generated C# DTOs.

Loading & Serialization

The Default Loading Behavior, any custom functionality defined in Data Sources, and [DtoIncludes] & [DtoExcludes] may also restrict which properties are sent to the client when requested.

NotMapped

While Coalesce does not do anything special for the [NotMapped] attribute, it is still an important attribute to keep in mind while building your model, as it prevents EF Core from doing anything with the property.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-types/dtos.html b/modeling/model-types/dtos.html new file mode 100644 index 000000000..56ccc0a2c --- /dev/null +++ b/modeling/model-types/dtos.html @@ -0,0 +1,111 @@ + + + + + + Custom DTOs | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Custom DTOs

In addition to the generated Generated C# DTOs that Coalesce will create for you, you may also create your own implementations of an IClassDto. These types are first-class citizens in Coalesce - you will get a full suite of features surrounding them as if they were entities. This includes generated API Controllers, Admin Views, and full TypeScript ViewModels and TypeScript List ViewModels.

The difference between a Custom DTO and the underlying entity that they represent is as follows:

  • The only time your custom DTO will be served is when it is requested directly from one of the endpoints on its generated controller, or when its type is explicitly used by a method or property of another type.

  • When mapping data from your database, or mapping data incoming from the client, the DTO itself must manually map all properties, since there is no corresponding Generated DTO. Attributes like [DtoIncludes] & [DtoExcludes] and property-level security through Security Attributes have no effect on custom DTOs, since those attribute only affect what get generated for Generated C# DTOs.

Creating a Custom DTO

To create a custom DTO, define a class annotated with [Coalesce] that implements IClassDTO<T>, where T is an EF Core POCO with a corresponding DbSet<T> on a DbContext that has also been exposed with [Coalesce]. Add any Properties to it just as you would add model properties to a regular EF model. If you are not exposing a DbContext class with [Coalesce] but still wish to create a Custom DTO based upon one of its entities, you can inherit from IClassDTO<T, TContext> instead as a means of explicitly declaring the type of the DbContext.

Next, ensure that one property is annotated with [Key] so that Coalesce can know the primary key of your DTO in order to perform database lookups and keep track of your object uniquely in the client-side TypeScript.

Now, populate the required MapTo and MapFrom methods with code for mapping from and to your DTO, respectively (the methods are named with respect to the underlying entity, not the DTO). Most properties probably map one-to-one in both directions, but you probably created a DTO because you wanted some sort of custom mapping - say, mapping a collection on your entity with a comma-delimited string on the DTO. This is also the place to perform any user-based, role-based, property-level security. You can access the current user on the IMappingContext object.

c#
[Coalesce]
+public class CaseDto : IClassDto<Case>
+{
+    [Key]
+    public int CaseId { get; set; }
+
+    public string Title { get; set; }
+
+    [Read]
+    public string AssignedToName { get; set; }
+
+    public void MapTo(Case obj, IMappingContext context)
+    {
+        obj.Title = Title;
+    }
+
+    public void MapFrom(Case obj, IMappingContext context = null, IncludeTree tree = null)
+    {
+        CaseId = obj.CaseKey;
+        Title = obj.Title;
+        if (obj.AssignedTo != null)
+        {
+            AssignedToName = obj.AssignedTo.Name;
+        }
+    }
+}

WARNING

Custom DTOs do not utilize property-level Security Attributes nor [DtoIncludes] & [DtoExcludes], since these are handled in the Generated DTOs. If you need property-level security or trimming, you must write it yourself in the MapTo and MapFrom methods.

If you have any child objects on your DTO, you can invoke the mapper for some other object using the static Mapper class. Also seen in this example is how to respect the Include Tree when mapping entity types; however, respecting the IncludeTree is optional. Since this DTO is a custom type that you've written, if you're certain your use cases don't need to worry about object graph trimming, then you can ignore the IncludeTree. If you do ignore the IncludeTree, you should pass null to calls to Mapper - don't pass in the incoming IncludeTree, as this could cause unexpected results.

c#
using IntelliTect.Coalesce.Mapping;
+
+[Coalesce]
+public class CaseDto : IClassDto<Case>
+{
+    public int ProductId { get; set; }
+    public Product Product { get; set; }
+    ...
+
+    public void MapFrom(Case obj, IMappingContext context = null, IncludeTree tree = null)
+    {
+        ProductId = obj.ProductId;
+
+        if (tree == null || tree[nameof(this.Product)] != null)
+            Product = Mapper.MapToDto<Product, ProductDtoGen>(obj.Product, context, tree?[nameof(this.Product)]
+        ...
+    }
+}

Using Custom DataSources and Behaviors

Declaring an IClassDto DataSource

When you create a custom DTO, it will use the Standard Data Source and Standard Behaviors just like any of your regular Entity Models. If you wish to override this, your custom data source and/or behaviors MUST be declared in one of the following ways:

  1. As a nested class of the DTO. The relationship between your data source or behaviors and your DTO will be picked up automatically.

    c#
    [Coalesce]
    +public class CaseDto : IClassDto<Case>
    +{
    +    [Key]
    +    public int CaseId { get; set; }
    +
    +    public string Title { get; set; }
    +    
    +    ...
    +
    +    public class MyCaseDtoSource : StandardDataSource<Case, AppDbContext>
    +    {
    +        ...
    +    }
    +}
  2. With a [DeclaredFor] attribute that references the DTO type:

    c#
    [Coalesce]
    +public class CaseDto : IClassDto<Case>
    +{
    +    [Key]
    +    public int CaseId { get; set; }
    +
    +    public string Title { get; set; }
    +    
    +    ...
    +}
    +
    +[Coalesce, DeclaredFor(typeof(CaseDto))]
    +public class MyCaseDtoSource : StandardDataSource<Case, AppDbContext>
    +{
    +    ...
    +}

ProjectedDtoDataSource

In addition to creating a Data Source by deriving from Standard Data Source, there also exists a class ProjectedDtoDataSource that can be used to easily perform projection from EF model types to your custom DTO types using EF query projections. ProjectedDtoDataSource inherits from Standard Data Source.

c#
[Coalesce, DeclaredFor(typeof(CaseDto))]
+public class CaseDtoSource : ProjectedDtoDataSource<Case, CaseDto, AppDbContext>
+{
+    public CaseDtoSource(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override IQueryable<CaseDto> ApplyProjection(IQueryable<Case> query, IDataSourceParameters parameters)
+    {
+        return query.Select(c => new CaseDto
+        {
+            CaseId = c.CaseKey,
+            Title = c.Title,
+            AssignedToName = c.AssignedTo == null ? null : c.AssignedTo.Name
+        });
+    }
+}

Surgical Saves

The Vue ViewModels support surgical saves through their $saveMode property, and Knockout ViewModels through the saveIncludedFields configuration.

Surgical saves require DTOs on the server that are capable of determining which of their properties have been set by the model binder, as surgical saves are sent from the client by entirely omitting properties from the x-www-form-urlencoded body that is sent to the server.

The Generated C# DTOs implement the necessary logic for this; however, any Custom DTOs must have this logic manually written by you, the developer. Either implement the same pattern that can be seen in the Generated C# DTOs, or do not use surgical saves with Custom DTOs.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-types/entities.html b/modeling/model-types/entities.html new file mode 100644 index 000000000..1e9f2fa5f --- /dev/null +++ b/modeling/model-types/entities.html @@ -0,0 +1,73 @@ + + + + + + Entity Models | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Entity Models

Models are the core business objects of your application - they serve as the fundamental representation of data in your application. The design of your models is very important. In Entity Framework Core (EF), data models are just Plain Old CLR Objects (POCOs).

Building a Data Model

To start building your data model that Coalesce will generate code for, follow the best practices for EF Core. Guidance on this topic is available in abundance in the Entity Framework Core documentation.

Don't worry about querying or saving data when you're just getting started - Coalesce will provide a lot of that functionality for you, and it is very easy to customize what Coalesce offers later. To get started, just build your POCOs and DbContext classes. Annotate your DbContext class with [Coalesce] so that Coalesce will discover it and generate code based off of your context for you.

Before you start building, you are highly encouraged to read the sections below. The linked pages explain in greater detail what Coalesce will build for you for each part of your data model.

Properties

Read Properties for an outline of the different types of properties that you may place on your models and the code that Coalesce will generate for each of them.

Attributes

Coalesce provides a number of C# attributes that can be used to decorate your model classes and their properties in order to customize behavior, appearance, security, and more. Coalesce also supports a number of annotations from System.ComponentModel.DataAnnotations.

Read Attributes to learn more.

Methods

You can place both static and interface methods on your model classes. Any public methods annotated with [Coalesce] will have a generated API endpoint and corresponding generated TypeScript members for calling this API endpoint. Read Methods to learn more.

Customizing CRUD Operations

Once you've got a solid data model in place, its time to start customizing the way that Coalesce will read your data, as well as the way that it will handle your data when processing creates, updates, and deletes.

Data Sources

The method by which you can control what data the users of your application can access through Coalesce's generated APIs is by creating custom data sources. These are classes that allow complete control over the way that data is retrieved from your database and provided to clients. Read Data Sources to learn more.

Behaviors

Behaviors in Coalesce are to mutating data as data sources are to reading data. Defining a behaviors class for a model allows complete control over the way that Coalesce will create, update, and delete your application's data in response to requests made through its generated API. Read Behaviors to learn more.

Standalone (non-EF) Entities

In Coalesce, Standalone Entities are entity types that are not based on Entity Framework. These types are discovered by Coalesce by annotating them with [Coalesce, StandaloneEntity].

For these types, you must define at least one custom Data Source, and optionally a Behaviors class as well. If no behaviors are defined, the type is implicitly read-only, equivalent to turning off create/edit/delete via the Security Attributes.

To define data sources and behaviors for Standalone Entities, it is recommended you inherit from StandardDataSource<T> and StandardBehaviors<T>, respectively. For example:

c#
[Coalesce, StandaloneEntity]
+public class StandaloneExample
+{
+    public int Id { get; set; }
+
+    [Search(SearchMethod = SearchAttribute.SearchMethods.Contains), ListText]
+    public string Name { get; set; } = "";
+
+    [DefaultOrderBy]
+    public DateTimeOffset Date { get; set; }
+
+    private static int nextId = 0;
+    private static ConcurrentDictionary<int, StandaloneExample> backingStore = new ConcurrentDictionary<int, StandaloneExample>();
+
+    public class DefaultSource : StandardDataSource<StandaloneExample>
+    {
+        public DefaultSource(CrudContext context) : base(context) { }
+
+        public override Task<IQueryable<StandaloneExample>> GetQueryAsync(IDataSourceParameters parameters)
+            => Task.FromResult(backingStore.Values.AsQueryable());
+    }
+
+    public class Behaviors : StandardBehaviors<StandaloneExample>
+    {
+        public Behaviors(CrudContext context) : base(context) { }
+
+        public override Task ExecuteDeleteAsync(StandaloneExample item)
+        {
+            backingStore.TryRemove(item.Id, out _);
+            return Task.CompletedTask;
+        }
+
+        public override Task ExecuteSaveAsync(SaveKind kind, StandaloneExample? oldItem, StandaloneExample item)
+        {
+            if (kind == SaveKind.Create)
+            {
+                item.Id = Interlocked.Increment(ref nextId);
+                backingStore.TryAdd(item.Id, item);
+            }
+            else
+            {
+                backingStore.TryRemove(item.Id, out _);
+                backingStore.TryAdd(item.Id, item);
+            }
+            return Task.CompletedTask;
+        }
+    }
+}

The above example is admittedly contrived, as it is unlikely that you would be using an in-memory collection as a data persistence mechanism. A more likely real-world scenario would be to inject an interface to some other data store. Data Source and Behavior classes are instantiated using your application's service provider, so any registered service can be injected.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-types/external-types.html b/modeling/model-types/external-types.html new file mode 100644 index 000000000..c2fb5a7f5 --- /dev/null +++ b/modeling/model-types/external-types.html @@ -0,0 +1,66 @@ + + + + + + External Types | Coalesce + + + + + + + + + + + + + + + +
Skip to content

External Types

In Coalesce, any type which is connected to your data model but is not directly part of it is considered to be an "external type".

The collection of external types for a data model looks like this:

  1. Take all of the api-served types in your data model. This includes Entity Models and Custom DTOs.
  2. Take all of the property types, method parameters, and method return types of these types.
  3. Any of these types which are not built-in scalar types and not one of the aforementioned api-served types are external types.
  4. For any external type discovered, any of the property types which qualify under the above rules are also external types.

WARNING

Be careful when using types that you do not own for properties and method returns in your data model. When Coalesce generates external type ViewModels and DTOs, it will not stop until it has exhausted all paths that can be reached by following public property types and method returns.

In general, you should only expose types that you have created so that you will always have full control over them. Mark any properties you don't wish to expose with [InternalUse], or make those members non-public.

Generated Code

For each external type found in your application's model, Coalesce will generate:

Example Data Model

For example, in the following scenario, the following classes are considered as external types:

  • PluginMetadata, exposed through a getter-only property on ApplicationPlugin.
  • PluginResult, exposed through a method return on ApplicationPlugin.

PluginHandler is not because it not exposed by the model, neither directly nor through any of the other external types.

c#
public class AppDbContext : DbContext {
+    public DbSet<Application> Applications { get; set; }
+    public DbSet<ApplicationPlugin> ApplicationPlugins { get; set; }
+}
+
+public class Application {
+    public int ApplicationId { get; set; }
+    public string Name { get; set; }
+    public ICollection<ApplicationPlugin> Plugins { get; set; }
+}
+
+public class ApplicationPlugin {
+    public int ApplicationPluginId { get; set; }
+    public int ApplicationId { get; set; }
+    public Application Application { get; set; }
+
+    public string TypeName { get; set; }
+
+    private PluginHandler GetInstance() => 
+        ((PluginHandler)Activator.CreateInstance(Type.GetType(TypeName)));
+
+    public PluginMetadata Metadata => GetInstance().GetMetadata();
+
+    public PluginResult Invoke(string action, string data) => GetInstance().Invoke(Application, action, data);
+}
+
+public abstract class PluginHandler { 
+    public abstract PluginMetadata GetMetadata();
+    public abstract PluginResult Invoke(Application app, string action, string data);
+}
+
+public abstract class PluginMetadata { 
+    public bool Name { get; set; }
+    public string Version { get; set; }
+    public ICollection<string> Actions { get; set; }
+}
+
+public abstract class PluginResult { 
+    public bool Success { get; set; }
+    public string Message { get; set; }
+}

Loading & Serialization

External types have slightly different behavior when undergoing serialization to be sent to the client. Unlike database-mapped types which are subject to the rules of Include Tree, external types ignore the Include Tree when being mapped to DTOs for serialization. Read External Type Caveats for a more detailed explanation of this exception.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/modeling/model-types/services.html b/modeling/model-types/services.html new file mode 100644 index 000000000..b7302f671 --- /dev/null +++ b/modeling/model-types/services.html @@ -0,0 +1,52 @@ + + + + + + Services | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Services

In a Coalesce application, you are likely to end up with a need for some API endpoints that aren't closely tied with your regular data model. While you could stick Static Methods on one of your entities, do so is detrimental to the organization of your code.

Instead, Coalesce allows you to generate API Controllers and a TypeScript client from a service. A service, in this case, is nothing more than a C# class or an interface with methods on it, annotated with [Coalesce, Service]. An implementation of this class or interface must be injectable from your application's service container, so a registration in Startup.cs is needed.

The instance methods of these services work just like other custom Methods in Coalesce, with one notable distinction: Instance methods don't operate on an instance of a model, but instead on a dependency injected instance of the service.

Generated Code

For each external type found in your application's model, Coalesce will generate:

  • An API controller with endpoints that correspond to the service's instance methods.
  • A TypeScript client containing the members outlined in Methods for invoking these endpoints.

Example Service

An example of a service might look something like this:

c#
[Coalesce, Service]
+public interface IWeatherService
+{
+    WeatherData GetWeather(string zipCode);
+}

With an implementation:

c#
public class WeatherService : IWeatherService
+{
+    public WeatherService(AppDbContext db)
+    {
+        this.db = db;
+    }
+
+    public WeatherData GetWeather(string zipCode)
+    {
+        // Assuming some magic HttpGet method that works as follows...
+        var response = HttpGet("http://www.example.com/api/weather/" + zipCode);
+        return response.Body.SerializeTo<WeatherData>();
+    }
+
+    public void MethodThatIsNotExposedBecauseItIsNotOnTheExposedInterface() {  }
+}

And a registration:

c#
public class Startup 
+{
+    public void ConfigureServices(IServiceCollection services)
+    {
+        services.AddCoalesce<AppDbContext>();
+        services.AddScoped<IWeatherService, WeatherService>();
+    }
+}

While it isn't required that an interface for your service exist (you can generate directly from the implementation), it is highly recommended that an interface be used. Interfaces increase testability and reduce risk of accidentally changing the signature of a published API, among other benefits.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/net-logo.svg b/net-logo.svg new file mode 100644 index 000000000..d204a0904 --- /dev/null +++ b/net-logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/stacks/agnostic/dtos.html b/stacks/agnostic/dtos.html new file mode 100644 index 000000000..440a53405 --- /dev/null +++ b/stacks/agnostic/dtos.html @@ -0,0 +1,26 @@ + + + + + + Generated C# DTOs | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Generated C# DTOs

Data Transfer Objects, or DTOs, allow for transformations of data from the data store into a format more suited for transfer and use on the client side. This often means trimming properties and flattening structures to provide a leaner over-the-wire experience. Coalesce aims to support this as seamlessly as possible.

Coalesce supports two types of DTOs:

  • DTOs that are automatically generated for each POCO database object. These are controlled via Attributes on the POCO. These are outlined below.
  • DTOs that you create with IClassDto. These are outlined at Custom DTOs.

Automatically Generated DTOs

Every class that is exposed through Coalesce's generated API will have a corresponding DTO generated for it. These DTOs are used to shuttle data back and forth to the client. They are generated classes that have nullable versions of all the properties on the POCO class.

[DtoIncludes] & [DtoExcludes] and the Includes String infrastructure can be used to indicate which properties should be transferred to the client in which cases, and Include Tree is used to dictate how these DTOs are constructed from POCOs retrieved from the database.

The [Read] and [Edit] attributes can be used to apply property-level security, which manifests as conditional logic in the mapping methods on the generated DTOs.

See the Security page to read more about property-level security, as well as all other security mechanisms in Coalesce.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/agnostic/generation.html b/stacks/agnostic/generation.html new file mode 100644 index 000000000..2b94e9b30 --- /dev/null +++ b/stacks/agnostic/generation.html @@ -0,0 +1,43 @@ + + + + + + Code Generation Overview | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Code Generation Overview

Coalesce's principal purpose is a code generation framework for automating the creation of the boring-but-necessary parts of a web application. Below, you find an overview of the different components of Coalesce's code generation features.

Running Code Generation

Coalesce's code generation is ran via a dotnet CLI tool, dotnet coalesce. In order to invoke this tool, you must have the appropriate references to the package that provides it in your .csproj file:

xml
<Project Sdk="Microsoft.NET.Sdk.Web">
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+
+    <!-- Necessary to use DotNetCliToolReference with modern framework versions -->
+    <DotnetCliToolTargetFramework>net7.0</DotnetCliToolTargetFramework>
+  </PropertyGroup>
+
+  ...
+
+  <ItemGroup>
+    <PackageReference Include="IntelliTect.Coalesce" Version="..." />
+  </ItemGroup>
+
+  <ItemGroup>
+    <DotNetCliToolReference Include="IntelliTect.Coalesce.Tools" Version="..." />
+  </ItemGroup>  
+</Project>

CLI Options

All configuration of the way that Coalesce interacts with your projects, including locating, analyzing, and producing generated code, is done in a json configuration file, coalesce.json. Read more about this file at Code Generation Configuration.

There are a couple of extra options which are only available as CLI parameters to dotnet coalesce. These options do not affect the behavior of the code generation - only the behavior of the CLI itself.

--debug - When this flag is specified when running dotnet coalesce, Coalesce will wait for a debugger to be attached to its process before starting code generation.

-v|--verbosity <level> - Set the verbosity of the output. Options are trace, debug, information, warning, error, critical, and none.

Generated Code

Coalesce will generate a full vertical stack of code for you:

Backend C#

API Controllers

For each of your Entity Models, Custom DTOs, and Services, an API controller is created in the /Api/Generated directory of your web project. These controllers provide a number of endpoints for interacting with your data.

These controllers can be secured at a high level using Security Attributes, and when applicable to the type, with Data Sources and Behaviors.

C# DTOs

For each of your Entity Models, a C# DTO class is created. These classes are used to hold the data that will be serialized and sent to the client, as well as data that has been received from the client before it has been mapped back to your EF POCO class.

See Generated C# DTOs for more information.

Frontend - Vue

An overview of the Vue generated code can be found at Vue Overview.

Frontend - Knockout

An overview of the legacy Knockout generated code can be found at Knockout Overview.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/agnostic/getting-started-modeling.html b/stacks/agnostic/getting-started-modeling.html new file mode 100644 index 000000000..50095b7e3 --- /dev/null +++ b/stacks/agnostic/getting-started-modeling.html @@ -0,0 +1,26 @@ + + + + + + Data Modeling | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Data Modeling

At this point, you can open up the newly-created solution in Visual Studio and run your application. However, your application won't do much without a data model, so you will probably want to do the following before running:

  • Create an initial Data Model by adding EF entity classes to the data project and the corresponding DbSet<> properties to AppDbContext. You will notice that the starter project includes a single model, Widget, to start with. Feel free to change this model or remove it entirely. Read Entity Models for more information about creating a data model.

  • Run dotnet ef migrations add Init (Init can be any name) in the data project to create an initial database migration.

  • Run Coalesce's code generation by either:

    • Running dotnet coalesce in the web project's root directory
    • Running the coalesce npm script (Vue) or gulp task (Knockout) in the Task Runner Explorer

You're now at a point where you can start creating your own pages!


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/disambiguation/external-view-model.html b/stacks/disambiguation/external-view-model.html new file mode 100644 index 000000000..7aff2525c --- /dev/null +++ b/stacks/disambiguation/external-view-model.html @@ -0,0 +1,26 @@ + + + + + + TypeScript External ViewModels | Coalesce + + + + + + + + + + + + + + + +
Skip to content

TypeScript External ViewModels

This is a disambiguation page for a concept in Coalesce that is implemented differently between the available front-end stack choices. Please select your preferred stack.

Vue

The Vue stack for Coalesce does not create dedicated ViewModels for External Types. The interfaces in the Model Layer are used as the only representation of External Types on the client.

Knockout

See: TypeScript External ViewModels


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/disambiguation/list-view-model.html b/stacks/disambiguation/list-view-model.html new file mode 100644 index 000000000..c9e266f7f --- /dev/null +++ b/stacks/disambiguation/list-view-model.html @@ -0,0 +1,26 @@ + + + + + + TypeScript List ViewModels | Coalesce + + + + + + + + + + + + + + + +
Skip to content

TypeScript List ViewModels

This is a disambiguation page for a concept in Coalesce that is implemented differently between the available front-end stack choices. Please select your preferred stack.

Vue

See: Vue ListViewModels

Knockout

See: Knockout ListViewModels


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/disambiguation/view-model.html b/stacks/disambiguation/view-model.html new file mode 100644 index 000000000..a8207e6c0 --- /dev/null +++ b/stacks/disambiguation/view-model.html @@ -0,0 +1,26 @@ + + + + + + TypeScript ViewModels | Coalesce + + + + + + + + + + + + + + + +
Skip to content

TypeScript ViewModels

This is a disambiguation page for a concept in Coalesce that is implemented differently between the available front-end stack choices. Please select your preferred stack.

Vue

See: Vue ViewModels

Knockout

See: Knockout ViewModels


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/ko/client/bindings.html b/stacks/ko/client/bindings.html new file mode 100644 index 000000000..9f51f29a9 --- /dev/null +++ b/stacks/ko/client/bindings.html @@ -0,0 +1,65 @@ + + + + + + Knockout Bindings | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Knockout Bindings

Coalesce provides a number of knockout bindings that make common model binding activities much easier.

Editors Note: On this page, some bindings are split into their requisite HTML component with their data-bind component listed immediately after. Keep this in mind when reading.

Input Bindings

select2Ajax

html
<select data-bind="
+    select2Ajax: personId, 
+    url: '/api/Person/list', 
+    idField: 'personId', 
+    textField: 'Name', 
+    object: person, 
+    allowClear: true
+"></select>

Creates a select2 dropdown using the specified url and fields that can be used to select an object from the endpoint specified. Additional complimentary bindings include:

url: string

The Coalesce List API url to call to populate the contents of the dropdown.

idField: string

The name of the field on each item in the results of the AJAX call which contains the ID of the option. The value of this field will be set on the observable specified for the main select2Ajax binding.

textField: string

The name of the field on each item in the results of the AJAX call which contains the text to be displayed for each option.

object?: KnockoutObservable<Coalesce.BaseViewModel | null>

An observable that holds the full object corresponding to the foreign key property being bound to. If the selected value changes, this will be set to null to avoid representation of incorrect data (unless setObject is used - see below).

setObject: boolean = false

If true, the observable specified by the object binding will be set to the selected data when an option is chosen in the dropdown. Binding itemViewModel is required if this binding is set.

Additionally, requests to the API to populate the dropdown will request the entire object, as opposed to only the two fields specified for idField and textField like is normally done when this binding is missing or set to false. To override this behavior and continue requesting only specific fields even when setObject is true, add fields=field1,field2,... to the query string of the url binding.

itemViewModel?: (new (newItem: object) => Coalesce.BaseViewModel)

A reference to the class that represents the type of the object held in the object observable. This is used when constructing new objects from the results of the API call. Not used if setObject is false or unspecified. For example, setObject: true, itemViewModel: ViewModels.Person.

pageSize: number = 25

The number of items to request in each call to the server.

format: string = '{0}'

A string containing the substring {0}, which will be replaced with the text value of an option in the dropdown list when the option is displayed.

selectionFormat: string = '{0}'

A string containing the substring {0}, which will be replaced with the text value of the selected option of the dropdown list.

cache: boolean = true

If true, a cache-busting querystring parameter will be included in AJAX requests.

selectOnClose: boolean = false

Directly maps to select2 option selectOnClose.

allowClear: boolean = true

Whether or not to allow the current select to be set to null. Directly maps to select2 option allowClear.

placeholder: string = 'select'

Placeholder when nothing is selected. Directly maps to select2 option placeholder.

openOnFocus: boolean = false

If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.

select2AjaxMultiple

html
<select multiple="multiple" data-bind="
+    select2AjaxMultiple: people, 
+    url: '/api/Person/list', 
+    idField: 'personId', 
+    textField: 'Name', 
+    itemViewModel: ViewModels.PersonCase
+"></select>

Creates a select2 multi-select input for choosing objects that participate as the foreign object in a many-to-many relationship with the current object. The primary select2AjaxMultiple binding takes the collection of items that make up the foreign side of the relationship. This is NOT the collection of the join objects (a.k.a. middle table objects) in the relationship.

Additional complimentary bindings include:

url: string

The Coalesce List API url to call to populate the contents of the dropdown. In order to only receive specific fields from the server, add fields=field1,field2,... to the query string of the url, ensuring that at least the idField and textField are included in that collection.

idField: string

The name of the field on each item in the results of the AJAX call which contains the ID of the option. The value of this field will be set as the key of the foreign object in the many-to-many relationship.

textField: string

The name of the field on each item in the results of the AJAX call which contains the text to be displayed for each option.

itemViewModel: (new (newItem: object) => Coalesce.BaseViewModel)

A reference to the class that represents the types in the supplied collection. For example, a many-to-many between Person and Case objects where Case is the object being bound to and Person is the type represented by a child collection, the correct value is ViewModels.Person. This is used when constructing new objects representing the relationship when a new item is selected.

pageSize: number = 25

The number of items to request in each call to the server.

format: string = '{0}'

A string containing the substring {0}, which will be replaced with the text value of an option in the dropdown list when the option is displayed.

selectionFormat: string = '{0}'

A string containing the substring {0}, which will be replaced with the text value of the selected option of the dropdown list.

cache: boolean = true

If true, a cache-busting querystring parameter will be included in AJAX requests.

selectOnClose: boolean = false

Directly maps to select2 option selectOnClose.

allowClear: boolean = true

Whether or not to allow the current select to be set to null. Directly maps to select2 option allowClear.

placeholder: string = 'select'

Placeholder when nothing is selected. Directly maps to select2 option placeholder.

openOnFocus: boolean = false

If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.

select2AjaxText

html
<select data-bind="
+    select2AjaxText: schoolName, 
+    url: '/api/Person/SchoolNames'
+"></select>

Creates a select2 dropdown against the specified url where the url returns a collection of string values that are potential selection candidates. The dropdown also allows the user to input any value they choose - the API simply serves suggested values.

url: string

The url to call to populate the contents of the dropdown. This should be an endpoint that returns one of the following:

  • A raw string[]
  • An object that conforms to { list: string[] }
  • An object that conforms to { object: string[] }
  • An object that conforms to { list: { [prop: string]: string } } where the value given to resultField is a valid property of the returned objects.
  • An object that conforms to { object: { [prop: string]: string } } where the value given to resultField is a valid property of the returned objects.

The url will also be passed a search parameter and a page parameter appended to the query string. The chosen endpoint is responsible for implementing this functionality. Page size is expected to be some fixed value. Implementer should anticipate that the requested page may be out of range.

The cases listed above that accept arrays of objects (as opposed to arrays of strings) require that the resultField binding is also used. These are designed for obtaining string values from objects obtained from the standard list endpoint.

resultField?: string

If provided, specifies a field on the objects returned from the API to pull the string values from. See examples in url above.

allowCustom: boolean = true

If false, the user's search input will not be presented as a valid selectable value; only the exact values obtained from the API endpoint will be selectable.

selectOnClose: boolean = false

Directly maps to select2 option selectOnClose.

allowClear: boolean = true

Whether or not to allow the current select to be set to null. Directly maps to select2 option allowClear.

placeholder: string = 'select'

Placeholder when nothing is selected. Directly maps to select2 option placeholder.

openOnFocus: boolean = false

If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.

select2

html
<select data-bind="select2: selectedNumber">
+    <option value="1">Option 1</option>
+    <option value="2">Option 2</option>
+</select>

Sets up a basic select2 dropdown on an HTML select element. Dropdown contents should be populated through other means - either using stock Knockout bindings or server-side static contents (via cshtml).

selectOnClose: boolean = false

Directly maps to select2 option selectOnClose.

allowClear: boolean = true

Whether or not to allow the current select to be set to null. Directly maps to select2 option allowClear.

placeholder: string = 'select'

Placeholder when nothing is selected. Directly maps to select2 option placeholder.

openOnFocus: boolean = false

If true, the dropdown will open when tabbed to. Browser support may be incomplete in some versions of IE.

datePicker

html
<div class="input-group date">
+    <input data-bind="datePicker: birthDate" type="text" class="form-control" id-prefix="datePicker" />
+    <span class="input-group-addon">
+        <span class="fa fa-calendar"></span>
+    </span>
+</div>

Creates a date/time picker for changing a moment.Moment property. The control used is bootstrap-datetimepicker

preserveDate: boolean = false

If true, the date portion of the moment.Moment object will be preserved by the date picker. Only the time portion will be changed by user input.

preserveTime: boolean = false

If true, the time portion of the moment.Moment object will be preserved by the date picker. Only the date portion will be changed by user input.

format: string = 'M/D/YY h:mm a'

Specify the moment-compatible format string to be used as the display format for the text value shown on the date picker. Defaults to M/D/YY h:mm a. Direct pass-through to bootstrap-datetimepicker.

sideBySide: boolean = false

If true, places the time picker next to the date picker, visible at the same time. Direct pass-through to corresponding bootstrap-datetimepicker option.

stepping: number = 1

Direct pass-through to corresponding bootstrap-datetimepicker option.

timeZone: string = ''

Direct pass-through to corresponding bootstrap-datetimepicker option.

keyBinds = { left: null, right: null, delete: null }

Override key bindings of the date picker. Direct pass-through to corresponding bootstrap-datetimepicker option.

updateImmediate: boolean = false

If true, the datePicker will update the underlying observable on each input change. Otherwise, the observable will only be changed when the datePicker loses focus (on blur).

saveImmediately

html
<div data-bind="with: product">
+    <input type="text" data-bind="textValue: description, saveImmediately: true" />
+</div>

When used in a context where $data is a Coalesce.BaseViewModel, that object's saveTimeoutMs configuration property (see ViewModel Configuration) will be set to 0 when the element it is placed on gains focus. This value will be reverted to its previous value when the element loses focus. This will cause any changes to the object, including any observable bound as input on the element, to trigger a save immediately rather than after a delay (defaults to 500ms).

delaySave

html
<div data-bind="with: product">
+    <input type="text" data-bind="textValue: description, delaySave: true" />
+</div>

When used in a context where $data is a Coalesce.BaseViewModel, that object's autoSaveEnabled configuration property (see ViewModel Configuration) will be set to false when the element it is placed on gains focus. This will cause any changes to the object, including any observable bound as input on the element, to not trigger auto saves while the element has focus. When the element loses focus, the autoSaveEnabled flag will be reverted to its previous value and an attempt will be made to save the object.

Display Bindings

tooltip

html
<div data-bind="tooltip: tooltipText">Some Element</div>
+<div data-bind="tooltip: {title: note, placement: 'bottom', animation: false}">Some Element</div>

Wrapper around the Bootstrap tooltip component. Binding can either be simply a string (or observable string), or it can be an object that will be passed directly to the Bootstrap tooltip component.

fadeVisible

html
<div data-bind="fadeVisible: isVisible">Some Element</div>

Similar to the Knockout visible binding, but uses jQuery fadeIn/fadeOut calls to perform the transition.

slideVisible

html
<div data-bind="slideVisible: isVisible">Some Element</div>

Similar to the Knockout visible, but uses jQuery slideIn/slideOut calls to perform the transition.

moment

html
<div data-bind="moment: momentObservable"></div>
+<div data-bind="moment: momentObservable, format: 'MM/DD/YYYY hh:mm a'"></div>

Controls the text of the element by calling the format method on a moment object.

momentFromNow

html
<div data-bind="momentFromNow: momentObservable"></div>
+<div data-bind="momentFromNow: momentObservable, shorten: true"></div>

Controls the text of the element by calling the fromNow method on a moment object. If shorten is true, certain phrases will be slightly shortened.

Utility Bindings

let

html
<div class="item">
+    <!-- ko let: { showControls: $data.isEditing() || $parent.editingChildren() } -->
+    <button data-bind="click: $root.editItem, visible: showControls">Edit</button>
+    <span data-bind="text: name"></span>
+    <button data-bind="click: $root.deleteItem, visible: showControls">Delete</button>
+    <!-- /ko -->
+</div>

The let binding is a somewhat common construct used in Knockout applications, but isn't part of Knockout itself. It effectively allows the creation of variables in the binding context, allowing complex statements which may be used multiple times to be aliased for both clarity of code and better performance.

Knockout Binding Defaults

Knockout Helpers

These are static properties on IntelliTect.Coalesce.Knockout.Helpers.Knockout you can assign to somewhere in the app lifecycle startup to change the default markup generated server-side when using @Knockout.* methods to render Knockout bindings in your .cshtml files.

public static int DefaultLabelCols { get; set; } = 3;

The default number of Bootstrap grid columns a field label should span across.

public static int DefaultInputCols { get; set; } = 9;

The default number of Bootstrap grid columns a form input should span across.

public static string DefaultDateFormat { get; set; } = "M/D/YYYY";

Sets the default date-only format to be used by all date/time pickers. This only applies to models with a date-only [DateType] attribute.

public static string DefaultTimeFormat { get; set; } = "h:mm a";

Sets the default time-only format to be used by all date/time pickers. This only applies to models with a time-only [DateType] attribute.

public static string DefaultDateTimeFormat { get; set; } = "M/D/YYYY h:mm a";

Sets the default date/time format to be used by all date/time pickers. This only applies to DateTimeOffset model properties that do not have a limiting [DateType] attribute.

Note

DefaultDateFormat, DefaultTimeFormat and DefaultDateTimeFormat all take various formatting strings from the Moment.js library. A full listing can be found on the Moment website.

Timezone

The date/time picker properties can be coupled with DateTimeOffset model properties to display time values localized for the current user's locale. If you want to make the localization static, simply include a script block in your _Layout.cshtml or in a specific view that sets the default for Moment.js:

html
<script>
+moment.tz.setDefault("America/Chicago");
+</script>

Note

This needs to happen after Moment is loaded, but before the bootstrap-datetimepicker script is loaded.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/ko/client/external-view-model.html b/stacks/ko/client/external-view-model.html new file mode 100644 index 000000000..97d45859a --- /dev/null +++ b/stacks/ko/client/external-view-model.html @@ -0,0 +1,33 @@ + + + + + + TypeScript External ViewModels | Coalesce + + + + + + + + + + + + + + + +
Skip to content

TypeScript External ViewModels

For all External Types in your model, Coalesce will generate a TypeScript class that provides a bare-bones representation of that type's properties.

These ViewModels are dependent on Knockout, and are designed to be used directly from Knockout bindings in your HTML. All data properties on the generated model are Knockout observables.

Base Members

The TypeScript ViewModels for external types do not have a common base class, and do not have any of the behaviors or convenience properties that the regular TypeScript ViewModels for database-mapped classes have.

Model-Specific Members

Data Properties


+public personId: KnockoutObservable<number | null> = ko.observable(null);
+public fullName: KnockoutObservable<string | null> = ko.observable(null);
+public gender: KnockoutObservable<number | null> = ko.observable(null);
+public companyId: KnockoutObservable<number | null> = ko.observable(null);
+public company: KnockoutObservable<ViewModels.Company | null> = ko.observable(null);
+public addresses: KnockoutObservableArray<ViewModels.Address> = ko.observableArray([]);
+public birthDate: KnockoutObservable<moment.Moment | null> = ko.observable(moment());

For each exposed property on the underlying EF POCO, a KnockoutObservable<T> property will exist on the TypeScript model. For navigation properties, these will be typed with the corresponding TypeScript ViewModel for the other end of the relationship. For collections (including collection navigation properties), these properties will be KnockoutObservableArray<T> objects.

Enum Members

For each enum property on your POCO, the following will be created:

public genderText: KnockoutComputed<string | null>

A KnockoutComputed<string> property that will provide the text to display for that property.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/ko/client/list-view-model.html b/stacks/ko/client/list-view-model.html new file mode 100644 index 000000000..84c2b3e86 --- /dev/null +++ b/stacks/ko/client/list-view-model.html @@ -0,0 +1,37 @@ + + + + + + TypeScript ListViewModels | Coalesce + + + + + + + + + + + + + + + +
Skip to content

TypeScript ListViewModels

In addition to TypeScript ViewModels for interacting with instances of your data classes in TypeScript, Coalesce will also generated a List ViewModel for loading searched, sorted, paginated data from the server.

These ListViewModels, like the ViewModels, are dependent on Knockout and are designed to be used directly from Knockout bindings in your HTML.

Base Members

The following members are defined on BaseListViewModel<> and are available to the ListViewModels for all of your model types:

modelKeyName: string

Name of the primary key of the model that this list represents.

includes: string

String that is used to control loading and serialization on the server. See Includes String for more information.

items: KnockoutObservableArray<TItem>

The collection of items that have been loaded from the server.

addNewItem: (): TItem

Adds a new item to the items collection.

deleteItem: (item: TItem): JQueryPromise<any>

Deletes an item and removes it from the items collection.

queryString: string

An arbitrary URL query string to append to the API call when loading the list of items.

Search criteria for the list. This can be easily bound to with a text box for easy search behavior. See [Search] for a detailed look at how searching works in Coalesce.

isLoading: KnockoutObservable<boolean>

True if the list is loading.

isLoaded: KnockoutObservable<boolean>

True once the list has been loaded.

load: (callback?: any): JQueryPromise<any>

Load the list using current parameters for paging, searching, etc Result is placed into the items property.

message: KnockoutObservable<string>

If a load failed, this is a message about why it failed.

getCount: (callback?: any): JQueryPromise<any>

Gets the count of items without getting all the items. Result is placed into the count property.

count: KnockoutObservable<number>

The result of getCount(), or the total on this page.

totalCount: KnockoutObservable<number>

Total count of items, even ones that are not on the page.

nextPage: (): void

Change to the next page.

nextPageEnabled: KnockoutComputed<boolean>

True if there is another page after the current page.

previousPage: (): void

Change to the previous page.

previousPageEnabled: KnockoutComputed<boolean>

True if there is another page before the current page.

page: KnockoutObservable<number>

Page number. This can be set to get a new page.

pageCount: KnockoutObservable<number>

Total page count

pageSize: KnockoutObservable<number>

Number of items on a page.

orderBy: KnockoutObservable<string>

Name of a field by which this list will be loaded in ascending order.

If set to "none", default sorting behavior, including behavior defined with use of [DefaultOrderBy] in C# POCOs, is suppressed.

orderByDescending: KnockoutObservable<string>

Name of a field by which this list will be loaded in descending order.

orderByToggle: (field: string): void

Toggles sorting between ascending, descending, and no order on the specified field.

Model-Specific Members

Configuration

static coalesceConfig: Coalesce.ListViewModelConfiguration<PersonList, ViewModels.Person>

A static configuration object for configuring all instances of the ListViewModel's type is created. See ViewModel Configuration.

coalesceConfig: Coalesce.ListViewModelConfiguration<PersonList, ViewModels.Person>

An per-instance configuration object for configuring each specific ListViewModel instance is created. See ViewModel Configuration.

Filter Object

public filter: {
+    personId?: string
+    firstName?: string
+    lastName?: string
+    gender?: string
+    companyId?: string
+} = null;

For each exposed scalar property on the underlying EF POCO, filter will have a corresponding property. If the filter object is set, requests made to the server to retrieve data will be passed all the values in this object via the URL's query string. These parameters will filter the resulting data to only rows where the parameter values match the row's values. For example, if filter.companyId is set to a value, only people from that company will be returned.

These parameters all allow for freeform string values, allowing the server to implement any kind of filtering logic desired. The Standard Data Source will perform the following depending on the property type:

  • Dates with a time component will be matched exactly.
  • Dates with no time component will match any dates that fell on that day.
  • Strings will match exactly unless an asterisk is found, in which case they will be matched with string.StartsWith.
  • Enums will match by string or numeric value. Multiple comma-delimited values will create a filter that will match on any of the provided values.
  • Numeric values will match exactly. Multiple comma-delimited values will create a filter that will match on any of the provided values.

Example usage:

ts
var list = new ListViewModels.PersonList();
+list.filter = { lastName: "Erickson" };
+list.load();

Static Method Members

public readonly namesStartingWith = new Person.NamesStartingWith(this);
+public static NamesStartingWith = class NamesStartingWith extends Coalesce.ClientMethod<PersonList, string[]> { ... };

For each exposed Static Method on your POCO, the members outlined in Methods - Generated TypeScript will be created.

DataSources


+public dataSources = ListViewModels.PersonDataSources;
+public dataSource: DataSource<Person> = new this.dataSources.Default();

For each of the Data Sources on the class, a corresponding class will be added to a namespace named ListViewModels.<ClassName>DataSources. This namespace can always be accessed on both ViewModel and ListViewModel instances via the dataSources property, and class instances can be assigned to the dataSource property.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/ko/client/methods.html b/stacks/ko/client/methods.html new file mode 100644 index 000000000..80f50850e --- /dev/null +++ b/stacks/ko/client/methods.html @@ -0,0 +1,30 @@ + + + + + + TypeScript Method Objects | Coalesce + + + + + + + + + + + + + + + +
Skip to content

TypeScript Method Objects

For each Custom Method you define, a class will be created on the corresponding TypeScript ViewModel (instance methods) or ListViewModel (static methods) that contains the properties and functions for interaction with the method. This class is accessible through a static property named after the method. An instance of this class will also be created on each instance of its parent - this instance is in a property with the camel-cased name of the method.

Here's an example for a method called Rename that takes a single parameter 'string name' and returns a string.

c#
public string Rename(string name)
+{
+    FirstName = name;
+    return FullName; // Return the new full name of the person.
+}

Base Members

The following members are available on the method object for all client methods:

public result: KnockoutObservable<string>

Observable that will contain the results of the method call after it is complete.

public rawResult: KnockoutObservable<Coalesce.ApiResult>

Observable with the raw, deserialized JSON result of the method call. If the method call returns an object, this will contain the deserialized JSON object from the server before it has been loaded into ViewModels and its properties loaded into observables.

public isLoading: KnockoutObservable<boolean>

Observable boolean which is true while the call to the server is pending.

public message: KnockoutObservable<string>

If the method was not successful, this contains exception information.

public wasSuccessful: KnockoutObservable<boolean>

Observable boolean that indicates whether the method call was successful or not.

ListResult<T> Base Members

For methods that return a ListResult<T>, the following additional members on the method object will be available:

public page: KnockoutObservable<number>

Page number of the results.

public pageSize: KnockoutObservable<number>

Page size of the results.

public pageCount: KnockoutObservable<number>

Total number of possible result pages.

public totalCount: KnockoutObservable<number>

Total number of results.

Method-specific Members

public static Rename = class Rename extends Coalesce.ClientMethod<Person, string> { ... }

Declaration of the method object class. This will be generated on the parent ViewModel or ListViewModel.

public readonly rename = new Person.Rename(this)

Default instance of the method for easy calling of the method without needing to manually instantiate the class. This will be generated on the parent ViewModel or ListViewModel.

public invoke: (name: string, callback: (result: string) => void = null, reload: boolean = true): JQueryPromise<any>

Function that takes all the method parameters and a callback. If reload is true, the ViewModel or ListViewModel that owns the method will be reloaded after the call is complete, and only after that happens will the callback be called.

public static Args = class Args { public name: KnockoutObservable<string> = ko.observable(null); }

Class with one observable member per method argument for binding method arguments to user input. Only generated for methods with arguments.

public args = new Rename.Args()

Default instance of the args class. Only generated for methods with arguments.

public invokeWithArgs: (args = this.args, callback?: (result: string) => void, reload: boolean = true) => JQueryPromise<any>

Function for invoking the method using the args class. The default instance of the args class will be used if none is provided. Only generated for methods with arguments.

public invokeWithPrompts: (callback: (result: string) => void = null, reload: boolean = true) => JQueryPromise<any>

Simple interface using browser prompt() input boxes to prompt the user for the required data for the method call. The call is then made with the data provided. Only generated for methods with arguments.

public resultObjectUrl: KnockoutObservable<string | null>

Observable that will contain an Object URL representing the last successful invocation result. Only generated for methods that return a file.

public url: KnockoutComputed<string>

The URL for the method. Can be useful for using as the src attribute of an image or video HTML element for file-downloading methods. Any arguments will be populated from this.args. Only generated for HTTP GET methods, as configured by [ControllerAction].


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/ko/client/model-config.html b/stacks/ko/client/model-config.html new file mode 100644 index 000000000..79353b84c --- /dev/null +++ b/stacks/ko/client/model-config.html @@ -0,0 +1,33 @@ + + + + + + ViewModel Configuration | Coalesce + + + + + + + + + + + + + + + +
Skip to content

ViewModel Configuration

A crucial part of the generated TypeScript ViewModels that Coalesce creates for you is the hierarchical configuration system that allows coarse-grained or fine-grained control over their behaviors.

Hierarchy

The configuration system has four levels where configuration can be performed, structured as follows:

Root Configuration

Coalesce.GlobalConfiguration: ModelConfiguration<any>
+Coalesce.GlobalConfiguration.app: AppConfiguration

The root configuration contains all configuration properties which apply to class category (TypeScript ViewModels, TypeScript ListViewModels, and Services). The app property contains global app configuration that exists independent of any models. Then, for each class kind, the following are available:

Root ViewModel/ListViewModel Configuration

Coalesce.GlobalConfiguration.viewModel: ViewModelConfiguration<BaseViewModel>
+Coalesce.GlobalConfiguration.listViewModel: ListViewModelConfiguration<BaseListViewModel<BaseViewModel>, BaseViewModel>
+Coalesce.GlobalConfiguration.serviceClient: ServiceClientConfiguration<ServiceClient>

Additional root configuration objects exist, one for each class kind. These configuration objects govern behavior that applies to only objects of these types. Root configuration can be overridden using these objects, although the practicality of doing so is dubious.

Class Configuration

ViewModels.ClassName.coalesceConfig: ViewModelConfiguration<ViewModels.ClassName>
+ListViewModels.ClassNameList.coalesceConfig: ListViewModelConfiguration<ListViewModels.ClassNameList, ViewModels.ClassName>
+Services.ServiceNameClient.coalesceConfig: ServiceClientConfiguration<ServiceName>

Each class kind has a static property named coalesceConfig that controls behavior for all instances of that class.

Instance Configuration

instance.coalesceConfig: ViewModelConfiguration<ViewModels.ClassName>
+listInstance.coalesceConfig: ListViewModelConfiguration<ListViewModels.ClassNameList, ViewModels.ClassName>
+serviceInstance.coalesceConfig: ServiceClientConfiguration<ServiceName>

Each instance of these classes also has a coalesceConfig property that controls behaviors for that instance only.

Evaluation

All configuration properties are Knockout ComputedObservable<T> objects. These observables behave like any other observable - call them with no parameter to obtain the value, call with a parameter to set their value.

Whenever a configuration property is read from, it first checks its own configuration object for the value of that property. If the explicit value for that configuration object is null, the parent's configuration will be checked for a value. This continues until either a value is found or the root configuration object is reached.

When a configuration property is given a value, that value is established on that configuration object only. Any dependent configuration objects will not be modified, and if those dependent configuration objects already have a value for that property, their existing value will be used unless that value is later set to null.

To obtain the raw value for a specific configuration property, call the raw() method on the observable: model.coalesceConfig.autoSaveEnabled.raw().

Available Properties & Defaults

The following configuration properties are available. Their default values are also listed. Note that all configuration properties are observables, but for simplicity the documentation below lists the underlying type.

Root Configuration

These properties on Coalesce.GlobalConfiguration are available to both ViewModelConfiguration, ListViewModelConfiguration, and ServiceClientConfiguration.

baseApiUrl: string = '/api'

The relative url where the API may be found.

baseViewUrl: string = ''

The relative url where the admin views may be found.

showFailureAlerts: boolean = true

Whether or not the callback specified for onFailure will be called or not.

onFailure: (obj, message) => alert(message)

A callback to be called when a failure response is received from the server.

onStartBusy: obj => Coalesce.Utilities.showBusy()

A callback to be called when an AJAX request begins.

onFinishBusy: obj => Coalesce.Utilities.hideBusy()

A callback to be called when an AJAX request completes.

App Configuration

These properties on Coalesce.GlobalConfiguration.app are not hierarchical - they govern the entire Coalesce application:

select2Theme: string | null = null

The theme parameter to select2's constructor when called by Coalesce's select2 Knockout Bindings.

ViewModelConfiguration

saveTimeoutMs: number = 500

Time to wait after a change is seen before auto-saving (if autoSaveEnabled is true). Acts as a debouncing timer for multiple simultaneous changes.

saveIncludedFields: string[] | null = null

An array of property names that, if set, will determine which fields will be sent to the server when saving. Only those values that are actually sent to the server will be mapped to the underlying entity.

This can improves the handling of concurrent changes being made by multiple users against different fields of the same entity. Specifically, if one page is designed to edit fields A and B, and another page is designed for editing fields C and D, you can configure this setting appropriately on each page to only save the corresponding fields.

Due to design limitations, this cannot be determined dynamically like it can with Vue's $saveMode property

WARNING

Surgical saves require DTOs on the server that are capable of determining which of their properties have been set by the model binder, as surgical saves are sent from the client by entirely omitting properties from the x-www-form-urlencoded body that is sent to the server.

The Generated C# DTOs implement the necessary logic for this; however, any Custom DTOs must have this logic manually written by you, the developer. Either implement the same pattern that can be seen in the Generated C# DTOs, or do not use surgical saves with Custom DTOs.

autoSaveEnabled: boolean = true

Determines whether changes to a model will be automatically saved after saveTimeoutMs milliseconds have elapsed.

autoSaveCollectionsEnabled: boolean = true

Determines whether or not changes to many-to-many collection properties will automatically trigger a save call to the server or not.

showBusyWhenSaving: boolean = false

Whether to invoke onStartBusy and onFinishBusy during saves.

loadResponseFromSaves: boolean = true

Whether or not to reload the ViewModel with the state of the object received from the server after a call to .save().

validateOnLoadFromDto: boolean = true

Whether or not to validate the model after loading it from a DTO from the server. Disabling this can improve performance in some cases.

setupValidationAutomatically: boolean = true

Whether or not validation on a ViewModel should be setup in its constructor, or if validation must be set up manually by calling viewModel.setupValidation(). Turning this off can improve performance in read-only scenarios.

onLoadFromDto: null | ((object: T) => void) = null

An optional callback to be called when an object is loaded from a response from the server. Callback will be called after all properties on the ViewModel have been set from the server response.

initialDataSource: null | DataSource<T> | (new () => DataSource<T>) = null

The dataSource (either an instance or a type) that will be used as the initial dataSource when a new object of this type is created. Not valid for global configuration; recommended to be used on class-level configuration. E.g. ViewModels.MyModel.coalesceConfig.initialDataSource(MyModel.dataSources.MyDataSource);

ListViewModelConfiguration

No special configuration is currently available for ListViewModels.

ServiceClientConfiguration

No special configuration is currently available for ServiceClients.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/ko/client/view-model.html b/stacks/ko/client/view-model.html new file mode 100644 index 000000000..b2d9a5e1f --- /dev/null +++ b/stacks/ko/client/view-model.html @@ -0,0 +1,47 @@ + + + + + + TypeScript ViewModels | Coalesce + + + + + + + + + + + + + + + +
Skip to content

TypeScript ViewModels

For each database-mapped type in your model, Coalesce will generate a TypeScript class that provides a multitude of functionality for interacting with the data on the client.

These ViewModels are dependent on Knockout, and are designed to be used directly from Knockout bindings in your HTML. All data properties on the generated model are Knockout observables.

Base Members

The following base members are available to all generated ViewModel classes:

includes: string

String that will be passed to the server when loading and saving that allows for data trimming via C# Attributes. See Includes String.

isChecked: KnockoutObservable<boolean>

Flag to use to determine if this item is checked. Only provided for convenience.

isSelected: KnockoutObservable<boolean>

Flag to use to determine if this item is selected. Only provided for convenience.

isEditing: KnockoutObservable<boolean>

Flag to use to determine if this item is being edited. Only provided for convenience.

toggleIsEditing () => void

Toggles the isEditing flag.

isExpanded: KnockoutObservable<boolean>

Flag to use to determine if this item is expanded. Only provided for convenience.

toggleIsExpanded: () => void

Toggles the isExpanded flag.

isVisible: KnockoutObservable<boolean>

Flag to use to determine if this item is shown. Only provided for convenience.

toggleIsSelected () => void

Toggles the isSelected flag.

selectSingle: (): boolean

Sets isSelected(true) on this object and clears on the rest of the items in the parent collection.

isDirty: KnockoutObservable<boolean>

Dirty Flag. Set when a value on the model changes. Reset when the model is saved or reloaded.

isLoaded: KnockoutObservable<boolean>

True once the data has been loaded.

isLoading: KnockoutObservable<boolean>

True if the object is loading.

isSaving: KnockoutObservable<boolean>

True if the object is currently saving.

isThisOrChildSaving: KnockoutComputed<boolean>

Returns true if the current object, or any of its children, are saving.

load: id: any, callback?: (self: T) => void): JQueryPromise<any> | undefined

Loads the object from the server based on the id specified. If no id is specified, the current id, is used if one is set.

loadChildren: callback?: () => void) => void

Loads any child objects that have an ID set, but not the full object. This is useful when creating an object that has a parent object and the ID is set on the new child.

loadFromDto: data: any, force?: boolean, allowCollectionDeletes?: boolean) => void

Loads this object from a data transfer object received from the server.

  • force - Will override the check against isLoading that is done to prevent recursion.
  • allowCollectionDeletes - Set true when entire collections are loaded. True is the default. In some cases only a partial collection is returned, set to false to only add/update collections.

deleteItem: callback?: (self: T) => void): JQueryPromise<any> | undefined

Deletes the object without any prompt for confirmation.

deleteItemWithConfirmation: callback?: () => void, message?: string): JQueryPromise<any> | undefined

Deletes the object if a prompt for confirmation is answered affirmatively.

errorMessage: KnockoutObservable<string>

Contains the error message from the last failed call to the server.

onSave: callback: (self: T) => void): boolean

Register a callback to be called when a save is done. Returns true if the callback was registered, or false if the callback was already registered.

saveToDto: () => any

Saves this object into a data transfer object to send to the server.

save: callback?: (self: T) => void): JQueryPromise<any> | boolean | undefined

Saves the object to the server and then calls a callback. Returns false if there are validation errors.

parent: any

Parent of this object, if this object was loaded as part of a hierarchy.

parentCollection: KnockoutObservableArray<T>

Parent of this object, if this object was loaded as part of list of objects.

editUrl: KnockoutComputed<string>

URL to a stock editor for this object.

showEditor: callback?: any): JQueryPromise<any>

Displays an editor for the object in a modal dialog.

validate: (): boolean

Triggers any validation messages to be shown, and returns a bool that indicates if there are any validation errors.

validationIssues: any

ValidationIssues returned from the server when trying to persist data

warnings: KnockoutValidationErrors

List of warnings found during validation. Saving is still allowed with warnings present.

errors: KnockoutValidationErrors

List of errors found during validation. Any errors present will prevent saving.

Model-Specific Members

The following members are generated for each generated ViewModel class and are unique to each class. The examples below are based on a type named Person.

Configuration

static coalesceConfig: Coalesce.ViewModelConfiguration<Person>

A static configuration object for configuring all instances of the ViewModel's type is created. See ViewModel Configuration.

coalesceConfig: Coalesce.ViewModelConfiguration<Person>

An per-instance configuration object for configuring each specific ViewModel instance is created. See ViewModel Configuration.

DataSources


+public dataSources = ListViewModels.PersonDataSources;
+public dataSource: DataSource<Person> = new this.dataSources.Default();

For each of the Data Sources for a model, a class will be added to a namespace named ListViewModels.<ClassName>DataSources. This namespace can always be accessed on both ViewModel and ListViewModel instances via the dataSources property, and class instances can be assigned to the dataSource property.

Data Properties


+public personId: KnockoutObservable<number | null> = ko.observable(null);
+public fullName: KnockoutObservable<string | null> = ko.observable(null);
+public gender: KnockoutObservable<number | null> = ko.observable(null);
+public companyId: KnockoutObservable<number | null> = ko.observable(null);
+public company: KnockoutObservable<ViewModels.Company | null> = ko.observable(null);
+public addresses: KnockoutObservableArray<ViewModels.Address> = ko.observableArray([]);
+public birthDate: KnockoutObservable<moment.Moment | null> = ko.observable(moment());

For each exposed property on the underlying EF POCO, a KnockoutObservable<T> property will exist on the TypeScript model. For navigation properties, these will be typed with the corresponding TypeScript ViewModel for the other end of the relationship. For collections (including collection navigation properties), these properties will be KnockoutObservableArray<T> objects.

Enum Members

For each enum property on your POCO, the following will be created:

public genderText: KnockoutComputed<string | null>

A KnockoutComputed<string> property that will provide the text to display for that property.

public genderValues: Coalesce.EnumValue[] = [ 
+    { id: 1, value: 'Male' },
+    { id: 2, value: 'Female' },
+    { id: 3, value: 'Other' },
+];

A static array of objects with properties id and value that represent all the values of the enum.


+export namespace Person {
+    export enum GenderEnum {
+        Male = 1,
+        Female = 2,
+        Other = 3,
+    };
+}

A TypeScript enum that mirrors the C# enum directly. This enum is in a sub-namespace of ViewModels named the same as the class name.

Collection Navigation Property Helpers

For each collection navigation property on the POCO, the following members will be created:

public addToAddresses: (autoSave?: boolean) => ViewModels.Address;

A method that will add a new object to that collection property. If autoSave is specified, the auto-save behavior of the new object will be set to that value. Otherwise, the inherited default will be used (see ViewModel Configuration)

public addressesListUrl: KnockoutComputed<string>;

A KnockoutComputed<string> that evaluates to a relative url for the generated table view that contains only the items that belong to the collection navigation property.

Reference Navigation Property Helpers

public showCompanyEditor: (callback?: any) => void;

For each reference navigation property on the POCO a method will be created that will call showEditor on that current value of the navigation property, or on a new instance if the current value is null.

public companyText: KnockoutComputed<string>;

For each reference navigation property, a KnockoutComputed<string> property will be created that will provide the text to display for that property. This will be the property on the class annotated with [ListText].

Instance Method Members

public readonly getBirthDate = new Person.GetBirthDate(this);
+public static GetBirthDate = class GetBirthDate extends Coalesce.ClientMethod<Person, moment.Moment> { ... };

For each Instance Method on your POCO, a class and instance member will be created as described in Methods - Generated TypeScript.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/ko/getting-started.html b/stacks/ko/getting-started.html new file mode 100644 index 000000000..f324a71a5 --- /dev/null +++ b/stacks/ko/getting-started.html @@ -0,0 +1,76 @@ + + + + + + Getting Started with Knockout | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Getting Started with Knockout

Creating a Project

WARNING

The Coalesce Knockout.js stack is deprecated and will be receiving only critical bug fixes going forward. You are strongly encouraged to start all new Coalesce projects with Vue.

The quickest and easiest way to create a new Coalesce Knockout application is to use the dotnet new template. In your favorite shell:

sh
dotnet new install IntelliTect.Coalesce.KnockoutJS.Template
+dotnet new coalesceko

View on GitHub

Data Modeling

At this point, you can open up the newly-created solution in Visual Studio and run your application. However, your application won't do much without a data model, so you will probably want to do the following before running:

  • Create an initial Data Model by adding EF entity classes to the data project and the corresponding DbSet<> properties to AppDbContext. You will notice that the starter project includes a single model, Widget, to start with. Feel free to change this model or remove it entirely. Read Entity Models for more information about creating a data model.

  • Run dotnet ef migrations add Init (Init can be any name) in the data project to create an initial database migration.

  • Run Coalesce's code generation by either:

    • Running dotnet coalesce in the web project's root directory
    • Running the coalesce npm script (Vue) or gulp task (Knockout) in the Task Runner Explorer

You're now at a point where you can start creating your own pages!

Building Pages & Features

Lets say we've created a model called Person as follows, and we've ran code generation with dotnet coalesce:

c#
namespace MyApplication.Data.Models 
+{
+    public class Person
+    {
+        public int PersonId { get; set; }
+        public string Name { get; set; }
+        public DateTimeOffset? BirthDate { get; set; }
+    }
+}

We can create a details page for a Person by creating:

  • A controller in src/MyApplication.Web/Controllers/PersonController.cs:

    c#
    namespace MyApplication.Web.Controllers
    +{
    +    public partial class PersonController
    +    {
    +        public IActionResult Details() => View();
    +    }
    +}
  • A view in src/MyApplication.Web/Views/Person/Details.cshtml:

    razor
    <h1>Person Details</h1>
    +
    +<div data-bind="with: person">
    +    <dl class="dl-horizontal">
    +        <dt>Name </dt>
    +        <dd data-bind="text: name"></dd>
    +
    +        <dt>Date of Birth </dt>
    +        <dd data-bind="moment: birthDate, format: 'MM/DD/YYYY hh:mm a'"></dd>
    +    </dl>
    +</div>
    +
    +@section Scripts
    +{
    +<script src="~/js/person.details.js"></script>
    +<script>
    +    $(function () {
    +        var vm = new MyApplication.PersonDetails();
    +        ko.applyBindings(vm);
    +        vm.load();
    +    });
    +</script>
    +}
  • And a script in src/MyApplication.Web/Scripts/person.details.ts:

    ts
    /// <reference path="viewmodels.generated.d.ts" />
    +
    +module MyApplication {
    +    export class PersonDetails {
    +        public person = new ViewModels.Person();
    +
    +        load() {
    +            var id = Coalesce.Utilities.GetUrlParameter("id");
    +            if (id != null && id != '') {
    +                this.person.load(id);
    +            }
    +        }
    +    }
    +}

With these pieces in place, we now have a functioning page that will display details about a person. We can start up the application and navigate to /Person/Details?id=1 (assuming a person with ID 1 exists - if not, navigate to /Person/Table and create one).

From this point, one can start adding more fields, more features, and more flair to the page. Check out all the other documentation in the sidebar to see what else Coalesce has to offer, including the Knockout Overview.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/ko/overview.html b/stacks/ko/overview.html new file mode 100644 index 000000000..297f283b1 --- /dev/null +++ b/stacks/ko/overview.html @@ -0,0 +1,26 @@ + + + + + + Knockout Overview | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Knockout Overview

The Knockout stack for Coalesce offers the ability to build pages with the time-tested Knockout JavaScript library using all of the features of the Coalesce generated APIs and ViewModels. It can be used for anything between adding simple interactive augmentations of MVC pages to building a full MPA-SPA hybrid application.

Getting Started

Check out Getting Started with Knockout if you haven't already to learn how to get a new Coalesce Knockout project up and running.

Generated Code

Below you will find a brief overview of each of the different pieces of code that Coalesce will generate for you when you choose the Knockout stack.

TypeScript

Coalesce generates a number of different types of TypeScript classes to support your data through the generated API.

ViewModels

One view model class is generated for each of your Entity Models and Custom DTOs. These models contain fields for your model Properties, and functions and other members for your model Methods. They also contain a number of standard fields & functions inherited from BaseViewModel which offer basic loading & saving functionality, as well as other handy utility members for use with Knockout.

See TypeScript ViewModels for more details.

List ViewModels

One ListViewModel is generated for each of your Entity Models and Custom DTOs. These classes contain functionality for loading sets of objects from the server. They provide searching, paging, sorting, and filtering functionality.

See TypeScript ListViewModels for more details.

External Type ViewModels

Any non-primitive types which are not themselves a Entity Models or Custom DTOs which are accessible through the aforementioned types, either through one of its Properties, or return value from one of its Methods, will have a corresponding TypeScript ViewModel generated for it. These ViewModels only provide a KnockoutObservable field for each property on the C# class.

See TypeScript External ViewModels for more details.

View Controllers

For each of your Entity Models and Custom DTOs, a controller is created in the /Controllers/Generated directory of your web project. These controllers provide routes for the generated admin views.

As you add your own pages to your application, you should add additional partial classes in the /Controllers that extend these generated partial classes to expose those pages.

Admin Views

For each of your Entity Models and Custom DTOs, a number of views are generated to provide administrative-level access to your data.

Table

Provides a basic table view with sorting, searching, and paging of your data. Can be rendered in either read-only mode (routed as /Table), or editable mode (routed as TableEdit).

Cards

Provides a card-based view of your data with searching and paging.

CreateEdit

Provides an editor view which can be used to create new entities or edit existing ones.

EditorHtml

Provides a minimal amount of HTML to display an editor for the object type. This is used by the showEditor method on the generated TypeScript ViewModels.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.html b/stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.html new file mode 100644 index 000000000..9f497aab4 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.html @@ -0,0 +1,39 @@ + + + + + + c-admin-audit-log-page | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-admin-audit-log-page

A full-featured page for interacting with Coalesce's Audit Logging. Presents a view similar to c-admin-table-page with content optimized for viewing audit log records. Designed to be routed to directly with vue-router.

Examples

ts
import { CAdminAuditLogPage } from 'coalesce-vue-vuetify3';
+const router = new Router({
+  // ...
+  routes: [
+    // ... other routes
+    {
+      path: '/admin/audit-logs',
+      component: CAdminAuditLogPage,
+      props: {
+        type: 'AuditLog'
+      }
+    },
+  ]
+})

Props

type: string

The PascalCase name of your IAuditLog implementation.

list?: ListViewModel

An optional ListViewModel that will be used if provided instead of the one the component will create automatically from the provided type prop.

color: string = 'primary'

A Vuetify color name to be applied to the toolbar at the top of the page.

Slots

row-detail: { item: AuditLogViewModel }

A slot that can be used to replace the entire content of the Detail column on the page.

row-detail-append: { item: AuditLogViewModel }

A slot that can be used to append additional content to the Detail column on the page.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-admin-display.html b/stacks/vue/coalesce-vue-vuetify/components/c-admin-display.html new file mode 100644 index 000000000..bdcd4a0d9 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-admin-display.html @@ -0,0 +1,33 @@ + + + + + + c-admin-display | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-admin-display

Behaves the same as c-display, except any collection navigation properties will be rendered as links to an admin list page, and any models will be rendered as a link to an admin item page.

Links for collections are resolved from vue-router with a route name of coalesce-admin-list, a type route param containing the name of the collection's type, and a query parameter filter.<foreign key name> with a value of the primary key of the owner of the collection. This route is expected to resolve to a c-admin-table-page, which is setup by default by the template outlined in Getting Started with Vue.

Links for single models are resolved from vue-router with a route name of coalesce-admin-item, a type route param containing the name of the model's type, and a id route param containing the object's primary key. This route is expected to resolve to a c-admin-editor-page, which is setup by default by the template outlined in Getting Started with Vue.

Examples

template
<!-- Renders regularly as text: -->
+<c-admin-display :model="person" for="firstName" />
+
+<!-- Renders as a link to an item: -->
+<c-admin-display :model="person" for="company" />
+
+<!-- Renders as a link to a list: -->
+<c-admin-display :model="person" for="casesAssigned" />

Props

Same as c-display.

Slots

Same as c-display.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-admin-editor-page.html b/stacks/vue/coalesce-vue-vuetify/components/c-admin-editor-page.html new file mode 100644 index 000000000..e8e7ae4b5 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-admin-editor-page.html @@ -0,0 +1,44 @@ + + + + + + c-admin-editor-page | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-admin-editor-page

A page for a creating/editing single ViewModel instance. Provides a c-admin-editor and a c-admin-methods for the instance. Designed to be routed to directly with vue-router.

Examples

ts
// router.ts or main.ts
+
+// WITHOUT Vuetify A la carte:
+import { CAdminEditorPage } from 'coalesce-vue-vuetify';
+// WITH Vuetify A-la-carte:
+import { CAdminEditorPage } from 'coalesce-vue-vuetify/lib';
+
+const router = new Router({
+    // ...
+    routes: [
+        // ... other routes
+        {
+            path: '/admin/:type/edit/:id?',
+            name: 'coalesce-admin-item',
+            component: CAdminEditorPage,
+            props: true,
+        },
+    ]
+})

Props

type: string

The PascalCase name of the type to be created/edited.

id?: number | string

The primary key of the item being edited. If null or not provided, the page will be creating a new instance of the provided type instead.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.html b/stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.html new file mode 100644 index 000000000..7117f9c7c --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-admin-editor.html @@ -0,0 +1,26 @@ + + + + + + c-admin-editor | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-admin-editor

An editor for a single ViewModel instance. Provides a c-input for each property of the model.

Does not automatically enable auto-save - if desired, this must be enabled by the implementor of this component.

Examples

template
<c-admin-editor :model="person" />

Props

model: ViewModel | ListViewModel

The ViewModel to render an editor for.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-admin-method.html b/stacks/vue/coalesce-vue-vuetify/components/c-admin-method.html new file mode 100644 index 000000000..a0e543744 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-admin-method.html @@ -0,0 +1,26 @@ + + + + + + c-admin-method | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-admin-method

Provides an interface for invoking a method and rendering its result, designed to be use in an admin page.

For each parameter of a method, a c-input will be rendered to accept the input of that parameter. A button is provided to trigger an invocation of the method, progress and errors are rendered with a c-loader-status, and results are rendered with c-display.

Examples

template
<c-admin-method :model="person" for="setTitle" auto-reload-model />

Props

for: string | Method

A metadata specifier for the method. One of:

  • A string with the name of the method belonging to model.
  • A direct reference to a method's metadata object.
  • A string in dot-notation that starts with a type name and ending with a method name.

model: ViewModel | ListViewModel

An ViewModel or ListViewModel owning the method and API Caller that was specified by the for prop.

autoReloadModel?: boolean = false

True if the model should have its $load invoked after a successful invocation of the method.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-admin-methods.html b/stacks/vue/coalesce-vue-vuetify/components/c-admin-methods.html new file mode 100644 index 000000000..bd0a35063 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-admin-methods.html @@ -0,0 +1,26 @@ + + + + + + c-admin-methods | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-admin-methods

Renders in a Vuetify v-expansion-panels a c-admin-method for each method on a ViewModel or ListViewModel.

Examples

template
<c-admin-methods :model="person" class="x" auto-reload-model />
template
<c-admin-methods :model="person" auto-reload-model />
template
<c-admin-methods :model="personList" auto-reload-model />

Props

model: ViewModel | ListViewModel

An ViewModel or ListViewModel whose methods should each render as a c-admin-method.

autoReloadModel?: boolean = false

True if the model should have its $load invoked after a successful invocation of any method.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.html b/stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.html new file mode 100644 index 000000000..c7c28c775 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.html @@ -0,0 +1,44 @@ + + + + + + c-admin-table-page | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-admin-table-page

A full-featured page for interacting with a ListViewModel. Provides a c-admin-table and a c-admin-methods for the list. Designed to be routed to directly with vue-router.

Examples

ts
// router.ts or main.ts
+
+// WITHOUT Vuetify A la carte:
+import { CAdminTablePage } from 'coalesce-vue-vuetify';
+// WITH Vuetify A-la-carte:
+import { CAdminTablePage } from 'coalesce-vue-vuetify/lib';
+
+const router = new Router({
+    // ...
+    routes: [
+        // ... other routes
+        {
+            path: '/admin/:type',
+            name: 'coalesce-admin-list',
+            component: CAdminTablePage,
+            props: true,
+        },
+    ]
+})

Props

type: string

The PascalCase name of the type to be listed.

list?: ListViewModel

An optional ListViewModel that will be used if provided instead of the one the component will otherwise create automatically.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-admin-table-toolbar.html b/stacks/vue/coalesce-vue-vuetify/components/c-admin-table-toolbar.html new file mode 100644 index 000000000..923927cb0 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-admin-table-toolbar.html @@ -0,0 +1,26 @@ + + + + + + c-admin-table-toolbar | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-admin-table-toolbar

A full-featured toolbar for a ListViewModel designed to be used on an admin page, including "Create" and "Reload" buttons, a c-list-range-display, a c-list-page, a search field, c-list-filters, and a c-list-page-size.

Examples

template
<c-admin-table-toolbar :list="personList" />
template
<c-admin-table-toolbar :list="personList" color="pink" :editable.sync="isEditable" />

Props

list: ListViewModel

The ListViewModel to render the toolbar for.

color: string = 'primary'

The color of the toolbar.

editable?: boolean

If provided, adds a button to toggle the value of this prop. Should be bound with the .sync modifier.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-admin-table.html b/stacks/vue/coalesce-vue-vuetify/components/c-admin-table.html new file mode 100644 index 000000000..33c66d1f0 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-admin-table.html @@ -0,0 +1,26 @@ + + + + + + c-admin-table | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-admin-table

An full-featured table for a ListViewModel, including a c-admin-table-toolbar, c-table, and c-list-pagination.

The table can be in read mode (default), or toggled into edit mode with the button provided by the c-admin-table-toolbar. When placed into edit mode, auto-save is enabled.

Examples

template
<c-admin-table :list="personList" />

Props

list: ListViewModel

The ListViewModel to render a table for.

pageSizes?: number[]

An optional list of available page sizes to offer through the c-list-pagination's c-list-page-size component. Defaults to [10, 25, 100].

queryBind?: boolean

If true, the Data Source Standard Parameters of the provided ListViewModel will be read from and written to the window's query string. The "Editable" state of the table will also be bound to the query string.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-datetime-picker.html b/stacks/vue/coalesce-vue-vuetify/components/c-datetime-picker.html new file mode 100644 index 000000000..653202963 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-datetime-picker.html @@ -0,0 +1,35 @@ + + + + + + c-datetime-picker | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-datetime-picker

A general, all-purpose date/time input component that can be used either with models and metadata or as a standalone component using only v-model.

Examples

template
<c-datetime-picker :model="person" for="birthDate" />
+
+<c-datetime-picker v-model="standaloneDate" />
+
+<c-datetime-picker 
+    v-model="standaloneTime" 
+    date-kind="time"
+    date-format="h:mm a"
+/>

Props

for?: string | DateProperty | DateValue

A metadata specifier for the value being bound. One of:

  • A string with the name of the value belonging to model.
  • A direct reference to a metadata object.
  • A string in dot-notation that starts with a type name.

model?: Model | DataSource

An object owning the value that was specified by the for prop. If provided, the input will be bound to the corresponding property on the model object.

value?: Date // Vue 2
+modelValue?: Date // Vue 3

If binding the component with v-model, accepts the value part of v-model.

dateKind?: 'date' | 'time' | 'datetime' = 'datetime'

Whether the date is only a date, only a time, or contains significant date and time information.

If the component was bound with metadata using the for prop, this will default to the kind specified by [DateType].

dateFormat?: string

The format of the date that will be rendered in the component's text field, and the format that will be attempted first when parsing user input in the text field.

Defaults to:

  • M/d/yyyy h:mm a if dateKind == 'datetime',
  • M/d/yyyy if dateKind == 'date', or
  • h:mm a if dateKind == 'time'.

WARNING

When parsing a user's text input into the text field, c-datetime-picker will first attempt to parse it with the format specified by dateFormat, or the default as described above if not explicitly specified.

If this fails, the date will be parsed with the Date constructor, but only if the dateKind is datetime or date. This works fairly well on all modern browsers, but can still occasionally have issues. c-datetime-picker tries its best to filter out bad parses from the Date constructor, like dates with a year earlier than 1000.

native?: boolean

True if a native HTML5 input should be used instead of a popup menu with Vuetify date/time pickers inside of it.

sideBySide?: boolean

True if the calendar and clock should be shown side by side in the picker menu, rather than in separate tabs.

readonly?: boolean

True if the component should be read-only.

disabled?: boolean

True if the component should be disabled.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-display.html b/stacks/vue/coalesce-vue-vuetify/components/c-display.html new file mode 100644 index 000000000..a51c0bdca --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-display.html @@ -0,0 +1,31 @@ + + + + + + c-display | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-display

A general-purpose component for displaying any Value by rendering the value to a string with the display functions from the Models Layer. For plain string and number values, usage of this component is largely superfluous. For all other value types including dates, booleans, enums, objects, and collections, it is very handy.

Examples

Typical usage, providing an object and a property on that object:

template
<c-display :model="person" for="gender" />

Customizing date formatting (view format patterns):

template
<c-display :model="person" for="birthDate" format="M/d/yyyy" />

A contrived example of using c-display to render the result of an API Caller:

template
<c-display 
+    :value="person.setFirstName.result" 
+    :for="person.$metadata.methods.setFirstName.return" 
+    element="div"
+/>

Displaying a standalone date value without a model or other source of metadata:

template
<c-display :value="dateProp" format="M/d/yyyy" />

Props

for: string | Property | Value

A metadata specifier for the value being bound. Either a direct reference to the metadata object, or a string with the name of the value belonging to model, or a string in dot-notation that starts with a type name.

model?: Model | DataSource

An object owning the value that was specified by the for prop.

format: DisplayOptions["format"]

Shorthand for :options="{ format: format }", allowing for specification of the format to be used when displaying dates.

See DisplayOptions for details on the options available for format.

options: DisplayOptions

Specify options for formatting some kinds of values, including dates. See DisplayOptions for details.

value: any // Vue 2
+modelValue: any // Vue 3

Can be provided the value to be displayed in conjunction with the for prop, as an alternative to the model prop.

This is an uncommon scenario - it is generally easier to use the for/model props together.

Slots

default - Used to display fallback content if the value being displayed is either null or "" (empty string).

[DataTypeAttribute]

For properties and other values annotated with [DataTypeAttribute], the following special handling occurs based on the data type:

  • DataType.MultilineText: Renders with white-space: pre-wrap.
  • DataType.Password: Renders with a show/hide toggle (hidden by default), showing a fixed number of dot characters when hidden.
  • DataType.Url: Renders as a clickable link.
  • DataType.EmailAddress: Renders as a clickable mailto link.
  • DataType.PhoneNumber: Renders as a clickable tel link.
  • DataType.ImageUrl: Renders as an img element.
  • "Color": Renders a colored dot next to the value, interpreting the field value as a 7-character HTML hex color code.

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-input.html b/stacks/vue/coalesce-vue-vuetify/components/c-input.html new file mode 100644 index 000000000..62aea9576 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-input.html @@ -0,0 +1,31 @@ + + + + + + c-input | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-input

A general-purpose input component for most Values. c-input does not have much functionality of its own - instead, it delegates to the right kind of component based on the type of value to which it is bound. This includes both other Coalesce Vuetify Components as well as direct usages of some Vuetify components.

All attributes are passed through to the delegated-to component, allowing for full customization of the underlying Vuetify component.

A summary of the components delegated to, by type:

Any other unsupported type will simply be displayed with c-display, unless a default slot is provided - in that case, the default slot will be rendered instead.

When bound to a ViewModel, the validation rules for the bound property will be obtained from the ViewModel and passed to Vuetify's rules prop.

Examples

Typical usage, providing an object and a property on that object:

template
<c-input :model="person" for="firstName" />

Customizing the Vuetify component used:

template
<c-input :model="comment" for="content" textarea solo />

Binding to API Caller args objects:

template
<c-input 
+    :model="person.setFirstName" 
+    for="newName" />

Or, using a more verbose syntax:

template
<c-input 
+    :model="person.setFirstName.args" 
+    for="Person.methods.setFirstName.newName" />

Binding to Data Source Parameters:

template
<c-input :model="personList.$dataSource" for="startsWith" />

Usage with v-model (this scenario is atypical - the model/for pair of props are used in almost all scenarios):

template
<c-input v-model="person.firstName" for="Person.firstName" />

Props

for?: string | Property | Value

A metadata specifier for the value being bound. One of:

  • A string with the name of the value belonging to model.
  • A direct reference to a metadata object.
  • A string in dot-notation that starts with a type name.

model?: Model | DataSource

An object owning the value that was specified by the for prop. If provided, the input will be bound to the corresponding property on the model object.

value?: any // Vue 2
+modelValue?: any // Vue 3

If binding the component with v-model, accepts the value part of v-model.

Slots

default - Used to display fallback content if c-input does not support the type of the value being bound. Generally this does not need to be used, as you should avoid creating c-input components for unsupported types in the first place.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-list-filters.html b/stacks/vue/coalesce-vue-vuetify/components/c-list-filters.html new file mode 100644 index 000000000..a3daed0cf --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-list-filters.html @@ -0,0 +1,26 @@ + + + + + + c-list-filters | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-list-filters

A component that provides an interface for modifying the filters prop of a ListViewModel's parameters.

Example Usage

template
<c-list-filters :list="list" />

Props

list: ListViewModel

The ListViewModel whose filters will be editable.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.html b/stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.html new file mode 100644 index 000000000..cde89217c --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-list-page-size.html @@ -0,0 +1,26 @@ + + + + + + c-list-page-size | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-list-page-size

A component that provides an dropdown for modifying the pageSize parameter prop of a ListViewModel.

Example Usage

template
<c-list-page-size :list="list" />

Props

list: ListViewModel

The ListViewModel whose pagination will be editable.

pageSizes?: number[]

An optional list of available page sizes to offer through c-list-page-size. Defaults to [10, 25, 100].


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-list-page.html b/stacks/vue/coalesce-vue-vuetify/components/c-list-page.html new file mode 100644 index 000000000..3131d1848 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-list-page.html @@ -0,0 +1,26 @@ + + + + + + c-list-page | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-list-page

A component that provides previous/next buttons and a text field for modifying the page parameter prop of a ListViewModel.

Example Usage

template
<c-list-page :list="list" />

Props

list: ListViewModel

The ListViewModel whose current page will be changeable with the component.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.html b/stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.html new file mode 100644 index 000000000..7425de60f --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-list-pagination.html @@ -0,0 +1,26 @@ + + + + + + c-list-pagination | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-list-pagination

A component that provides an interface for modifying the pagination parameters of a ListViewModel.

This is a composite of c-list-page-size, c-list-range-display, and c-list-page, arranged horizontally. It is designed to be used above or below a table (e.g. c-table).

Example Usage

template
<c-list-pagination :list="list" />

Props

list: ListViewModel

The ListViewModel whose pagination will be editable.

pageSizes?: number[]

An optional list of available page sizes to offer through c-list-page-size. Defaults to [10, 25, 100].


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-list-range-display.html b/stacks/vue/coalesce-vue-vuetify/components/c-list-range-display.html new file mode 100644 index 000000000..69e63a9c4 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-list-range-display.html @@ -0,0 +1,26 @@ + + + + + + c-list-range-display | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-list-range-display

Displays pagination information about the current $items of a ListViewModel in the format <start index> - <end index> of <total count>.

Uses the pagination information returned from the last successful $load call, not the current $params of the ListViewModel.

Examples

template
<c-list-range-display :list="list" />

Props

list: ListViewModel

The ListViewModel to display pagination information for.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-loader-status.html b/stacks/vue/coalesce-vue-vuetify/components/c-loader-status.html new file mode 100644 index 000000000..654a93788 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-loader-status.html @@ -0,0 +1,69 @@ + + + + + + c-loader-status | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-loader-status

A component for displaying progress and error information for one or more API Callers.

TIP

It is highly recommended that all API Callers utilized by your application that don't have any other kind of error handling should be represented by a c-loader-status so that users can be aware of any errors that occur.

Progress is indicated with a Vuetify v-progress-linear component, and errors are displayed in a v-alert. Transitions are applied to smoothly fade between the different states the the caller can be in.

Examples

Wrap contents of a details/edit page:

template
<h1>Person Details</h1>
+<c-loader-status
+    :loaders="{ 
+        'no-initial-content no-error-content': [person.$load],
+        '': [person.$save, person.$delete],
+    }"
+    #default
+>
+    First Name: {{ person.firstName }}
+    Last Name: {{ person.lastName }}
+    Employer: {{ person.company.name }}
+</c-loader-status>

Use c-loader-status to render a progress bar and any error messages, but don't use it to control content:

template
<c-loader-status :loaders="list.$load" />

Wrap a save/submit button:

template
<c-loader-status :loaders="[person.$save, person.$delete]" no-loading-content>
+    <button> Save </button>
+    <button> Delete </button>
+</c-loader-status>

Hides the table before the first load has completed, or if loading the list encountered an error. Don't show the progress bar after we've already loaded the list for the first time (useful for loads that occur without user interaction, e.g. setInterval):

template
<c-loader-status
+    :loaders="list.$load"
+    no-initial-content 
+    no-error-content
+    no-secondary-progress 
+>
+    <table>
+        <tr v-for="item in list.$items"> ... </tr>
+    </table>
+</c-loader-status>

Props

loaders: 
+  // Flags per component:
+  | ApiCaller 
+  | ApiCaller[]
+  // Flags per caller:
+  | { [flags: string]: ApiCaller | ApiCaller[] } 

This prop has multiple options that support simple or complex usage scenarios:

Flags Per Component

A single instance, or array of API Callers, whose status will be represented by the component. The flags for these objects will be determined from the component-level flag props.

template
<c-loader-status
+  :loaders="[product.$load, person.$load]"
+  no-initial-content
+  no-error-content
+/>

Flags Per Caller

A more advanced usage allows passing different flags for different callers. Provide a dictionary object with entries mapping zero or more flags to one or more API Callers. Multiple entries of flags/caller pairs may be specified in the dictionary to give different behavior to different API callers. These flags are layered on top of the base flag props.

template
<c-loader-status
+  :loaders="{ 
+    'no-initial-content no-error-content': [person.$load],
+    'no-loading-content': [person.$save, person.$delete],
+  }"
+/>

progressPlaceholder: boolean = true

Specify if space should be reserved for the progress indicator. If set to false, the content in the default slot may jump up and down slightly as the progress indicator shows and hides.

progressAbsolute: boolean = false

Positions the progress bar absolutely. This can be useful in compact interfaces where extra space for the progress bar is undesirable, allowing the progress bar to potentially overlap content while active.

height: number = 10

Specifies the height in pixels of the v-progress-linear used to indicate progress.


+no-loading-content?: boolean;
+no-error-content?: boolean;
+no-initial-content?: boolean;
+no-progress?: boolean;
+no-initial-progress?: boolean;
+no-secondary-progress?: boolean;

Component level flags options that control behavior when the simple form of loaders (single instance or array) is used, as well as provide baseline defaults that can be overridden by the advanced form of loaders (object map) .

Flags

The available flags are as follows, all of which default to true. In the object literal syntax for loaders, the no- prefix may be omitted to set the flag to true.

Flag
Description
no-loading-contentControls whether the default slot is rendered while any API caller is loading (i.e. when caller.isLoading === true).
no-error-contentControls whether the default slot is rendered while any API Caller is in an error state (i.e. when caller.wasSuccessful === false).
no-initial-contentControls whether the default slot is rendered while any API Caller has yet to receive a response for the first time (i.e. when caller.wasSuccessful === null).
no-progressMaster toggle for whether the progress indicator is shown in any scenario.
no-initial-progressControls whether the progress indicator is shown when an API Caller is loading for the very first time (i.e. when caller.wasSuccessful === null).
no-secondary-progressControls whether the progress indicator is shown when an API Caller is loading any time after its first invocation (i.e. when caller.wasSuccessful !== null).

Slots

default - Accepts the content whose visibility is controlled by the state of the supplied API Callers. It will be shown or hidden according to the flags defined for each caller.

TIP

(Vue 2 Only): Define the default slot as a scoped slot (e.g. with #default or v-slot:default on the c-loader-status) to prevent the VNode tree from being created when the content should be hidden. This improves performance and helps avoid null reference errors that can be caused when trying to render objects that haven't been loaded yet.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-select-many-to-many.html b/stacks/vue/coalesce-vue-vuetify/components/c-select-many-to-many.html new file mode 100644 index 000000000..4437f0721 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-select-many-to-many.html @@ -0,0 +1,35 @@ + + + + + + c-select-many-to-many | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-select-many-to-many

A multi-select dropdown component that allows for selecting values fetched from the generated /list API endpoints for collection navigation properties that were annotated with [ManyToMany].

TIP

It is unlikely that you'll ever need to use this component directly - it is highly recommended that you use c-input instead and let it delegate to c-select-many-to-many for you.

Examples

template
<c-select-many-to-many :model="case" for="caseProducts" />
template
<c-select-many-to-many 
+    :model="case" 
+    for="caseProducts" 
+    dense
+    outlined
+/>
template
<c-select-many-to-many 
+    v-model="case.caseProducts" 
+    for="Case.caseProducts" 
+/>

Props

for: string | Property | Value

A metadata specifier for the value being bound. One of:

  • A string with the name of the value belonging to model.
  • A direct reference to a metadata object.
  • A string in dot-notation that starts with a type name.

Note

c-select-many-to-many expects metadata for the "real" collection navigation property on a model. If you provide it the string you passed to [ManyToMany], an error wil be thrown.

model?: Model

An object owning the value that was specified by the for prop. If provided, the input will be bound to the corresponding property on the model object.

value?: any // Vue 2
+modelValue?: any // Vue 3

If binding the component with v-model, accepts the value part of v-model.

params?: ListParameters

An optional set of Data Source Standard Parameters to pass to API calls made to the server.

cache?: ResponseCachingConfiguration | boolean

If provided and non-false, enables response caching on the component's internal API caller.

Events

The following events and automatic API calls are only used when bound to a model that has auto-saves enabled.

  • adding - Fired when a new item has been selected, but before the call to /save has completed.
  • added - Fired when the call to /save has completed after adding a new item.
  • deleting - Fired when an item has been removed, but before the call to /delete has completed.
  • deleted - Fired when the call to /delete has completed after removing an item.

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-select-string-value.html b/stacks/vue/coalesce-vue-vuetify/components/c-select-string-value.html new file mode 100644 index 000000000..0675e59cf --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-select-string-value.html @@ -0,0 +1,52 @@ + + + + + + c-select-string-value | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-select-string-value

A dropdown component that will present a list of suggested string values from a custom API endpoint. Allows users to input values that aren't provided by the endpoint.

Effectively, this is a server-driven autocomplete list.

Examples

template
<c-select-string-value 
+    :model="person" 
+    for="jobTitle"
+    method="getSuggestedJobTitles"
+/>
template
<c-select-string-value 
+    v-model="title"
+    label="Job Title"
+    for="Person"
+    method="getSuggestedJobTitles"
+/>
c#
class Person 
+{
+    public int PersonId { get; set; } 
+
+    public string JobTitle { get; set; }
+
+    [Coalesce]
+    public static async Task<ICollection<string>> GetSuggestedJobTitles(AppDbContext db, string search) 
+    {
+        return await db.People
+            .Select(p => p.JobTitle)
+            .Distinct()
+            .Where(t => t.StartsWith(search))
+            .OrderBy(t => t)
+            .Take(100)
+            .ToListAsync()
+    }
+}

Props

for: string | Property | Value

A metadata specifier for the value being bound. One of:

  • A string with the name of the value belonging to model.
  • A direct reference to a metadata object.
  • A string in dot-notation that starts with a type name.

model: Model

An object owning the value that was specified by the for prop. If provided, the input will be bound to the corresponding property on the model object.

method: string

The camel-cased name of the Custom Method to invoke to get the list of valid values. Will be passed a single string parameter search. Must be a static method on the type of the provided model object that returns a collection of strings.

params?: DataSourceParameters

An optional set of Data Source Standard Parameters to pass to API calls made to the server.

listWhenEmpty?: boolean = false

True if the method should be invoked and the list displayed when the entered search term is blank.

eager?: boolean = false

True if the bound value should be updated as the user types. Otherwise, the bound value is updated when focus is lost or when a suggested value is chosen. This is only applicable for Vuetify 2 - in Vuetify 3, this is the default behavior.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-select-values.html b/stacks/vue/coalesce-vue-vuetify/components/c-select-values.html new file mode 100644 index 000000000..3f16145e1 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-select-values.html @@ -0,0 +1,30 @@ + + + + + + c-select-values | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-select-values

A multi-select input component for collections of non-object values (primarily strings and numbers).

TIP

It is unlikely that you'll ever need to use this component directly - it is highly recommended that you use c-input instead and let it delegate to c-select-values for you.

Examples

template
<c-select-values 
+    :model="post.setTags.args" 
+    for="Post.methods.setTags.params.tagNames" 
+/>

Props

for: string | CollectionProperty | CollectionValue

A metadata specifier for the value being bound. One of:

  • A string with the name of the value belonging to model.
  • A direct reference to a metadata object.
  • A string in dot-notation that starts with a type name.

model?: Model

An object owning the value that was specified by the for prop.

value?: any // Vue 2
+modelValue?: any // Vue 3

If binding the component with v-model, accepts the value part of v-model.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-select.html b/stacks/vue/coalesce-vue-vuetify/components/c-select.html new file mode 100644 index 000000000..c9c433712 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-select.html @@ -0,0 +1,60 @@ + + + + + + c-select | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-select

A dropdown component that allows for selecting values fetched from the generated /list API endpoints.

Used both for selecting values for foreign key and navigation properties, and for selecting arbitrary objects or primary keys independent of a parent or owning object.

Examples

Binding to a navigation property or foreign key of a model:

template
  <c-select :model="person" for="company" />
+  <!-- OR: -->
+  <c-select :model="person" for="companyId" />

Binding an arbitrary primary key value or an arbitrary object:

template
  <!-- Binding a key: -->
+  <c-select for="Person" :key-value.sync="selectedPersonId" />
+
+  <!-- Binding an object: -->
+  <c-select for="Person" :object-value.sync="selectedPerson" />
+  <c-select for="Person" v-model="selectedPerson" />

Examples of other props:

template
<c-select 
+  for="Person" 
+  v-model="selectedPerson"
+  :clearable="false"
+  preselect-first
+  :params="{ pageSize: 42, filter: { isActive: true } }"
+  :create="createMethods"
+  dense
+  outlined
+  color="pink"
+/>
+<!-- `createMethods` is defined in the docs of `create` below -->

Props

for: string | ForeignKeyProperty | ModelReferenceNavigationProperty | ModelType

A metadata specifier for the value being bound. One of:

  • The name of a foreign key or reference navigation property belonging to model.
  • The name of a model type.
  • A direct reference to a metadata object.
  • A string in dot-notation that starts with a type name that resolves to a foreign key or reference navigation property.

TIP

When binding by a key value, if the corresponding object cannot be found (e.g. there is no navigation property, or the navigation property is null), c-select will automatically attempt to load the object from the server so it can be displayed in the UI.

model?: Model

An object owning the value that was specified by the for prop. If provided, the input will be bound to the corresponding property on the model object.

If for specifies a foreign key or reference navigation property, both the foreign key and the navigation property of the model will be updated when the selected value is changed.

value?: any // Vue 2
+modelValue?: any // Vue 3

When binding the component with v-model, accepts the value part of v-model. If for was specified as a foreign key, this will expect a key; likewise, if for was specified as a type or as a navigation property, this will expect an object.

keyValue?: any

When bound with :key-value.sync="keyValue", allows binding the primary key of the selected object explicitly.

objectValue?: any

When bound with :object-value.sync="objectValue", allows binding the selected object explicitly.

clearable?: boolean

Whether the selection can be cleared or not, emitting null as the input value.

If not specified and the component is bound to a foreign key or reference navigation property, defaults to whether or not the foreign key has a required validation rule defined in its Metadata.

preselectFirst?: boolean = false

If true, then when the first list results for the component are received by the client just after the component is created, c-select will emit the first item in the list as the selected value.

preselectSingle?: boolean = false

If true, then when the first list results for the component are received by the client just after the component is created, if the results contained exactly one item, c-select will emit that only item as the selected value.

reloadOnOpen?: boolean = false

If true, the list results will be reloaded when the dropdown menu is opened. By default, list results are loaded when the component is mounted and also when any of its parameters change (either search input or the params prop).

params?: ListParameters

An optional set of Data Source Standard Parameters to pass to API calls made to the server.

cache?: ResponseCachingConfiguration | boolean

If provided and non-false, enables response caching on the component's internal API callers.

create?: {
+  getLabel: (search: string, items: TModel[]) => string | false,
+  getItem: (search: string, label: string) => Promise<TModel>
+}

A object containing a pair of methods that allowing users to create new items from directly within the c-select if a matching object is not found.

The object must contain the following two methods. You should define these in your component's script section - don't try to define them inline in your component.

create.getLabel: (search: string, items: TModel[]) => string | false

A function that will be called with the user's current search term, as well as the collection of currently loaded items being presented to the user as valid selection options.

It should return either a string that will be presented to the user as an option in the dropdown that can be clicked to invoke the getItem function below, or it should return false to prevent such an option from being shown to the user.

create.getItem: (search: string, label: string) => Promise<TModel>

A function that will be invoked when the user clicks the option in the dropdown list described by getLabel. It will be given the user's current search term as well as the value of the label returned from getLabel as parameters. It must perform the necessary operations to create the new object on the server and then return a reference to that object.

For example:

ts
createMethods = {
+  getLabel(search: string, items: Person[]) {
+    const searchLower = search.toLowerCase();
+    if (items.some(a => a.name?.toLowerCase().indexOf(searchLower) == 0)) {
+      return false;
+    }
+    return search;
+  },
+  async getItem(search: string, label: string) {
+    const client = new PersonApiClient();
+    return (await client.addPersonByName(label)).data.object!;
+  }
+}

Slots

#item="{ item, search }" - Slot used to customize the text of both items inside the list, as well as the text of selected items. By default, items are rendered with c-display. Slot is passed a parameter item containing a model instance, and search containing the current search query.

#list-item="{ item, search }" - Slot used to customize the text of items inside the list. If not provided, falls back to the item slot.

#selected-item="{ item, search }" - Slot used to customize the text of selected items. If not provided, falls back to the item slot.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/components/c-table.html b/stacks/vue/coalesce-vue-vuetify/components/c-table.html new file mode 100644 index 000000000..58b3e3b52 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/components/c-table.html @@ -0,0 +1,42 @@ + + + + + + c-table | Coalesce + + + + + + + + + + + + + + + +
Skip to content

c-table

A table component for displaying the contents of a ListViewModel. Also supports modifying the list's sort parameters by clicking on column headers. Pairs well with a c-list-pagination.

Example Usage

A simple table, rendering the items of a ListViewModel:

template
<c-table :list="list" />

A more complex example using more of the available options:

template
<c-table
+  :list="list"
+  :props="['firstName', 'lastName']"
+  :extra-headers="['Actions']"
+>
+  <template #item.append="{item}"> 
+    <td>
+      <v-btn
+        title="Edit"
+        text icon
+        :to="{name: 'edit-person', params: { id: item.$primaryKey }}"
+      >
+        <i class="fa fa-edit"></i>
+      </v-btn>
+    </td>
+  </template>
+</c-table>

Props

list: ListViewModel

The ListViewModel to display pagination information for.

props?: string[]

If provided, specifies which properties, and their ordering, should be given a column in the table.

If not provided, all non-key columns that aren't annotated with [Hidden(HiddenAttribute.Areas.List)] are given a column.

extraHeaders?: string[]

The text contents of one or more extra th elements to render in the table. Should be used in conjunction with the item.append slot.

editable: boolean = false

If true, properties in each table cell will be rendered with c-input. Non-editable properties will be rendered in accordance with the value of the admin prop.

admin: boolean = false

If true, properties in each table cell will be rendered with c-admin-display instead of c-display.

Slots

item.append - A slot rendered after the td elements on each row that render the properties of each item in the table. Should be provided zero or more additional td elements. The number should match the number of additional headers provided to the extraHeaders prop.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/coalesce-vue-vuetify/overview.html b/stacks/vue/coalesce-vue-vuetify/overview.html new file mode 100644 index 000000000..38dcd3ef4 --- /dev/null +++ b/stacks/vue/coalesce-vue-vuetify/overview.html @@ -0,0 +1,26 @@ + + + + + + Vuetify Components | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Vuetify Components

The Vue stack for Coalesce provides a set of components based on Vuetify, packaged up in an NPM package coalesce-vue-vuetify2 or coalesce-vue-vuetify3. These components are driven primarily by the Metadata Layer, and include both low level input and display components like c-input and c-display that are highly reusable in the custom pages you'll build in your application, as well as high-level components like c-admin-table-page and c-admin-editor-page that constitute entire pages.

Setup

All Coalesce projects should be started from the template described in Getting Started with Vue, and will therefore have all the setup completed for you.

If for whatever reason you find yourself adding Coalesce to an existing project, use the template as a reference for what configuration needs to be added to your project.

Display Components

ComponentDescription

c-display

A general-purpose component for displaying any Value by rendering the value to a string with the display functions from the Models Layer. For plain string and number values, usage of this component is largely superfluous. For all other value types including dates, booleans, enums, objects, and collections, it is very handy.

c-loader-status

A component for displaying progress and error information for one or more API Callers.

TIP

It is highly recommended that all API Callers utilized by your application that don't have any other kind of error handling should be represented by a c-loader-status so that users can be aware of any errors that occur.

c-list-range-display

Displays pagination information about the current $items of a ListViewModel in the format <start index> - <end index> of <total count>.

c-table

A table component for displaying the contents of a ListViewModel. Also supports modifying the list's sort parameters by clicking on column headers. Pairs well with a c-list-pagination.

Input Components

ComponentDescription

c-input

A general-purpose input component for most Values. c-input does not have much functionality of its own - instead, it delegates to the right kind of component based on the type of value to which it is bound. This includes both other Coalesce Vuetify Components as well as direct usages of some Vuetify components.

c-select

A dropdown component that allows for selecting values fetched from the generated /list API endpoints.

Used both for selecting values for foreign key and navigation properties, and for selecting arbitrary objects or primary keys independent of a parent or owning object.

c-datetime-picker

A general, all-purpose date/time input component that can be used either with models and metadata or as a standalone component using only v-model.

c-select-many-to-many

A multi-select dropdown component that allows for selecting values fetched from the generated /list API endpoints for collection navigation properties that were annotated with [ManyToMany].

c-select-string-value

A dropdown component that will present a list of suggested string values from a custom API endpoint. Allows users to input values that aren't provided by the endpoint.

Effectively, this is a server-driven autocomplete list.

c-select-values

A multi-select input component for collections of non-object values (primarily strings and numbers).

c-list-filters

A component that provides an interface for modifying the filters prop of a ListViewModel's parameters.

c-list-pagination

A component that provides an interface for modifying the pagination parameters of a ListViewModel.

This is a composite of c-list-page-size, c-list-range-display, and c-list-page, arranged horizontally. It is designed to be used above or below a table (e.g. c-table).

c-list-page-size

A component that provides an dropdown for modifying the pageSize parameter prop of a ListViewModel.

c-list-page

A component that provides previous/next buttons and a text field for modifying the page parameter prop of a ListViewModel.

Admin Components

ComponentDescription

c-admin-method

Provides an interface for invoking a method and rendering its result, designed to be use in an admin page.

c-admin-methods

Renders in a Vuetify v-expansion-panels a c-admin-method for each method on a ViewModel or ListViewModel.

c-admin-display

Behaves the same as c-display, except any collection navigation properties will be rendered as links to an admin list page, and any models will be rendered as a link to an admin item page.

c-admin-editor

An editor for a single ViewModel instance. Provides a c-input for each property of the model.

c-admin-editor-page

A page for a creating/editing single ViewModel instance. Provides a c-admin-editor and a c-admin-methods for the instance. Designed to be routed to directly with vue-router.

c-admin-table

An full-featured table for a ListViewModel, including a c-admin-table-toolbar, c-table, and c-list-pagination.

c-admin-table-toolbar

A full-featured toolbar for a ListViewModel designed to be used on an admin page, including "Create" and "Reload" buttons, a c-list-range-display, a c-list-page, a search field, c-list-filters, and a c-list-page-size.

c-admin-table-page

A full-featured page for interacting with a ListViewModel. Provides a c-admin-table and a c-admin-methods for the list. Designed to be routed to directly with vue-router.

c-admin-audit-log-page

A full-featured page for interacting with Coalesce's Audit Logging. Presents a view similar to c-admin-table-page with content optimized for viewing audit log records. Designed to be routed to directly with vue-router.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/getting-started.html b/stacks/vue/getting-started.html new file mode 100644 index 000000000..4ed516692 --- /dev/null +++ b/stacks/vue/getting-started.html @@ -0,0 +1,64 @@ + + + + + + Getting Started | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Getting Started

Creating a Project

The quickest and easiest way to create a new Coalesce Vue application is to use the dotnet new template. In your favorite shell:

sh
dotnet new install IntelliTect.Coalesce.Vue.Template
+dotnet new coalescevue -o MyCompany.MyProject
+cd MyCompany.MyProject/*.Web
+npm ci
  Static Badge

Project Structure

Important

The Vue template is based on Vite. You are strongly encouraged to read through at least the first few pages of the Vite Documentation before getting started on any development.

The structure of the Web project follows the conventions of both ASP.NET Core and Vite. The Vue-specific folders are as follows:

  • /src - Files that should be compiled into your application. CSS/SCSS, TypeScript, Vue SFCs, and so on.
  • /public - Static assets that should be served as files. Includes index.html, the root document of the application.
  • /wwwroot - Target for compiled output.

During development, no special tooling is required to build your frontend code. Coalesce's UseViteDevelopmentServer in ASP.NET Core will take care of that automatically when the application starts. Just make sure NPM packages have been installed (npm ci).

Data Modeling

At this point, you can open up the newly-created solution in Visual Studio and run your application. However, your application won't do much without a data model, so you will probably want to do the following before running:

  • Create an initial Data Model by adding EF entity classes to the data project and the corresponding DbSet<> properties to AppDbContext. You will notice that the starter project includes a single model, Widget, to start with. Feel free to change this model or remove it entirely. Read Entity Models for more information about creating a data model.

  • Run dotnet ef migrations add Init (Init can be any name) in the data project to create an initial database migration.

  • Run Coalesce's code generation by either:

    • Running dotnet coalesce in the web project's root directory
    • Running the coalesce npm script (Vue) or gulp task (Knockout) in the Task Runner Explorer

You're now at a point where you can start creating your own pages!

Building Pages & Features

Lets say we've created a model called Person as follows, and we've ran code generation with dotnet coalesce:

c#
namespace MyApplication.Data.Models 
+{
+    public class Person
+    {
+        public int PersonId { get; set; }
+        public string Name { get; set; }
+        public DateTimeOffset? BirthDate { get; set; }
+    }
+}

We can create a details page for a Person by creating a Single File Component in MyApplication.Web/src/views/person-details.vue:

vue
<template>
+  <dl>
+    <dt>Name</dt>
+    <dd>
+      <c-display :model="person" for="name" />
+    </dd>
+
+    <dt>Date of Birth</dt>
+    <dd>
+      <c-display :model="person" for="birthDate" format="M/d/yyyy" />
+    </dd>
+  </dl>
+</template>
+
+<script setup lang="ts"> 
+import { PersonViewModel } from "@/viewmodels.g";
+
+const props = defineProps<{ id: number }>();
+const person = new PersonViewModel();
+
+person.$load(props.id);
+</script>

Note

In the code above, c-display is a component that comes from the Vuetify Components for Coalesce.

For simple property types like string and number you can always use simple template interpolation syntax, but for more complex properties like dates, c-display is handy to use because it includes features like built-in date formatting.

We then need to add route to this new view. In MyApplication.Web/src/router.ts, add a new item to the routes array:

ts
// In the `routes` array, add the following item:
+{
+  path: '/person/:id',
+  name: 'person-details',
+  component: () => import('@/views/person-details.vue'),
+  props: route => ({ id: +route.params.id }),
+},

With these pieces in place, we now have a functioning page that will display details about a person. We can start up the application (or, if it was already running, refresh the page) and navigate to /person/1 (assuming a person with ID 1 exists - if not, navigate to /admin/Person and create one).

From this point, you can start adding more fields, more features, and more flair to the page. Check out all the other documentation in the sidebar to see what else Coalesce has to offer, including the Vue Overview.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/layers/api-clients.html b/stacks/vue/layers/api-clients.html new file mode 100644 index 000000000..d062a032f --- /dev/null +++ b/stacks/vue/layers/api-clients.html @@ -0,0 +1,77 @@ + + + + + + Vue API Client Layer | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Vue API Client Layer

The API client layer, generated as api-clients.g.ts, exports a class for each API controller that was generated for your data model. These classes are stateless and provide one method for each API endpoint. This includes both the standard set of endpoints created for Entity Models and Custom DTOs, as well as any custom Methods on the aforementioned types, as well as any methods on your Services.

The API clients provided by Coalesce are based on axios. All API clients used a shared axios instance, exported from coalesce-vue as AxiosClient. This instance can be used to configure all HTTP requests made by Coalesce, including things like attaching interceptors to modify the requests being made, or configuring defaults.

As with all the layers, the source code of coalesce-vue is also a great supplement to this documentation.

Concepts

API Client

A class, generated for each controller-backed type in your data model as <ModelName>ApiClient and exported from api-clients.g.ts containing one method for each API endpoint.

Each method on the API client takes in the regular parameters of the method as you would expect, as well as an optional AxiosRequestConfig parameter at the end that can be used to provide additional configuration for the single request, if needed.

For the methods that correspond to the standard set of CRUD endpoints that Coalesce provides (get, list, count, save, delete), an additional parameter parameters is available that accepts the set of Standard Parameters appropriate for the endpoint.

Each method returns a Promise<AxiosResponse<TApiResult>> where TApiResult is either ItemResult, ItemResult<T>, or ListResult<T>, depending on the return type of the API endpoint. AxiosResponse is the response object from axios, containing the TApiResult in its data property, as well as other properties like headers. The returned type T is automatically converted into valid Model implementations for you.

API Callers/API States

A stateful function for invoking an API endpoint, created with the $makeCaller function on an API Client. API Callers provide a wide array of functionality that is useful for working with API endpoints that are utilized by a user interface.

Because they are such an integral part of the overall picture of coalesce-vue, they have their own section below where they are explained in much greater detail.

API Callers

API Callers (typed with the name ApiState in coalesce-vue, sometimes also referred to as "loaders" or "invokers") are stateful functions for invoking an API endpoint, created with the $makeCaller function on an API Client. A summary of features:

Endpoint Invocation

Each API Caller is itself a function, so it can be invoked to trigger an API request to the server.

State management

API Callers contain properties about the last request made, including things like wasSuccessful, isLoading, result, and more.

Concurrency Management

Using setConcurrency(mode), you can configure how each individual caller handles what happens when multiple requests are made simultaneously

Argument Binding

API Callers can be created so that they have an args object that can be bound to, using .invokeWithArgs() to make a request using those arguments as the API endpoint's parameters. The API Callers created for the ViewModel Layer are all created this way.

Creating and Invoking an API Caller

API Callers can be created with the $makeCaller method of an API Client. The way in which it was created affects how it is invoked, as the parameters that the caller accepts are defined when it is created.

TIP

During typical development, it is unlikely that you'll need to make a custom API Caller - the ones created for you on the generated ViewModel Layer will usually suffice. However, creating your own can allow for some more advanced functionality.

Some examples:

ts
// Preamble for all the examples below:
+import { PersonApiClient } from '@/api-clients.g';
+const client = new PersonApiClient;

A caller that takes no additional parameters:

ts
const caller = client.$makeCaller(
+    "item", 
+    c => c.namesStartingWith("A")
+);
+
+await caller();
+console.log(caller.result)

A caller that takes custom parameters:

ts
const caller = client.$makeCaller(
+    methods => methods.namesStartingWith, 
+    (c, str: string) => c.namesStartingWith(str)
+);
+
+await caller("Rob");
+console.log(caller.result)

A caller that has an args object that can be bound to. This is how the generated API Callers in the ViewModel Layer are created:

ts
const caller = client.$makeCaller("item", 
+    // The parameter-based version is always required, even if it won't be used.
+    (c, str: string) => c.namesStartingWith(str),
+    // A function which creates a blank instance of the args object.
+    // All props should be initialized (i.e. not undefined) to work with Vue's reactivity.
+    () => ({str: null as string | null, }),
+    // The function that accepts the args object and uses it:
+    (c, args) => c.namesStartingWith(args.str)
+);
+
+caller.args.str = "Su";
+await caller.invokeWithArgs();
+console.log(caller.result)

A caller that performs multiple async operations:

ts
const deleteFirstNameStartingWith = client.$makeCaller(
+    "item",
+    async (c, str: string) => {
+        const namesResult = await c.namesStartingWith(str)
+        return await c.deletePersonByName(namesResult.data.object[0])
+    }
+);
+
+await caller("Rob");
+console.log(caller.result)

The first parameter, resultType, can either be one of "item" or "list", indicating whether the method returns a ItemResult or ListResult (examples #1 and #3 above). It can also be a function which accepts the set of method metadata for the API Client and which returns the specific method metadata (example #2 above), or it can be a direct reference to a specific method metadata object.

Properties

The following state properties can be found on API Caller instances. These properties are useful for binding to in a user interface to display errors, results, or indicators of progress.

All Callers

isLoading: boolean

True if there is currently a request pending for the API Caller.

wasSuccessful: boolean | null

A boolean indicating if the last request made was successful, or null if either no request has been made yet, or if a request has been made but has not yet completed.

message: string | null

An error message from the last request, if any. Will be set to null upon successful completion of a request.

hasResult: boolean

True if result is non-null. This prop is useful in performance-critical scenarios where checking result directly will cause an overabundance of re-renders in high-churn scenarios.

args: {}

Holds an object for the arguments of the function, and will be used if the caller is invoked with its invokeWithArgs() method. Useful for binding the arguments of a caller to inputs in a user interface.

Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.

get url(): string

Returns the URL for the method's HTTP endpoint. Any parameters are sourced from the args object. Useful for binding file-returning HTTP GET methods directly to image or video HTML elements.

Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.


ItemResult-based Callers

result: T | null

The principal data returned by the previous request. Will be set to null if the last response received returned no data (e.g. if the response was an error response)

validationIssues: ValidationIssue[] | null

Any validation issues returned by the previous request. This is never populated automatically by Coalesce, and is therefore is only used if you have written custom code to populate it in your Behaviors or Methods.


ListResult-based Callers

result: Array<T> | null

The principal data returned by the previous request. Will be set to null if the last response received returned no data (e.g. if the response was an error response).

page, pageSize, pageCount, totalCount: number | null

Properties which contain the pagination information returned by the previous request.

Concurrency Mode

setConcurrency(mode: 'disallow' | 'debounce' | 'cancel' | 'allow')

API callers have a setConcurrency method that allows you to customize how they behave when additional invocations are performed when there is already a request pending. There are four options available, with "disallow" being the default:

"disallow"

The default behavior - simply throws an error for any secondary invocations.

Note

Having "disallow" as the default prevents the unexpected behavior that can happen in a number of ways with the other modes:

  • For requests that are performing data-mutating actions on the server, all other concurrency modes could lead to an unexpected end state of the data due to requests either being abandoned, cancelled, or potentially happening out-of-order.
  • Throwing errors for multiple concurrent requests quickly surfaces issues during development where concurrent requests are not being correctly guarded against in a user interface - e.g. not disabling a "Save" or "Submit" button while the request is pending, which would otherwise lead to double-posts.
"debounce"

When a secondary invocation is performed, enqueue it after the current pending invocation completes.

If additional invocations are performed while there is already an invocation enqueued and waiting, the already-enqueued invocation is abandoned and replaced by the most recent invocation attempt. The promise of the abandoned invocation will be resolved with undefined (it is NOT rejected).

"cancel"

When a secondary invocation is performed, cancel the current pending invocation.

This completely aborts the request, propagating all the way back to the server where cancellation can be observed with HttpContext.RequestAborted. The promise of the cancelled invocation will be resolved with undefined (it is NOT rejected).

"allow"

When a secondary invocation is performed, always continue normally, sending the request to the server.

The state of the properties on the caller at any time will reflect the most recent response received from the server, which is never guaranteed to correlate with the most recent request made to the server - that is, requests are not guaranteed to complete in the order they were made. In particular, the isLoading property will be false after the first response comes back, even if the second response has not yet been received.

WARNING

For the reasons outlined above, it is generally not recommended to use "allow" unless you fully understand the drawbacks. This mode mirrors the legacy behavior of the Knockout stack for Coalesce.

Response Caching

Response caching on API Callers is a feature that will save API responses to persistent storage (sessionStorage or localStorage). The next time a matching request is made, the result property of the API Caller will be populated with that saved response, allowing for a faster time to interactivity and reduced repaints and shifting of elements as initial data loads after a page navigation. It does not prevent any HTTP requests from being made, and does not affect the Promise returned from invoke or invokeWithArgs.

Common use cases include:

  • Site-wide status or alert messages
  • Server-provided configuration
  • Dashboard data, like statistics or graphs

When a cached response is loaded, result is populated with that response's data, wasSuccessful and hasResult are set to true, and onFulfilled callbacks are invoked.

useResponseCaching(configuration?: ResponseCachingConfiguration | false)

Enables response caching on the API Caller. Only HTTP GET methods are supported, and file-returning methods are not supported. Call with false to disable caching after it was previously enabled. The available options are as follows:

ts
export type ResponseCachingConfiguration = {
+  /** Function that will determine the cache key used for a particular request.
+   * Return a falsy value to prevent caching. The default key is the request URL.
+   */
+  key?: (
+    req: AxiosRequestConfig,
+    defaultKey: string
+  ) => string | null | undefined;
+
+  /** The maximum age of a cached response. If null, the entry will not expire. Default 1 hour.
+   *
+   * The smallest of the current configured max age and the max age that was set at the time of the cached response is used. */
+  maxAgeSeconds?: number | null;
+
+  /** The Storage (default `sessionStorage`) that will hold cached responses. */
+  storage?: Storage;
+};

Other Methods

API Callers have a few other methods available as well:

cancel(): void

Manually cancel the current request. The promise of the cancelled invocation will be resolved with undefined (it is NOT rejected). If using concurrency mode "allow", only the most recent invocation is cancelled.

onFulfilled((state: TInvoker) => void | Promise<any>): void

Add a callback to the caller to be invoked when a success response is received from the server. If a promise is returned, this promise will be awaited and will delay the setting of the isLoading prop to false until it completes.

onRejected((state: TInvoker) => void | Promise<any>): void

Add a callback to the caller to be invoked when a failure response is received from the server. If a promise is returned, this promise will be awaited and will delay the setting of the isLoading prop to false until it completes.

invoke(...args: TArgs)

The invoke function is a reference from the caller to itself. In other words, caller.invoke === caller. This exists to mirror the syntax of the Knockout generated method classes.

invokeWithArgs(args?: {})

If called a parameter, that parameter will be used as the args object. Otherwise, caller.args will be used.

Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.

getResultObjectUrl(vue?: Vue): string | undefined

If the method returns a file, this method will return an Object URL representing the value of the result prop.

Accepts a Vue instance in order to manage the lifecycle of the URL, since object URLs must be manually released to avoid memory leaks. When the provided Vue component is destroyed, the object URL will be destroyed. If called inside the component template, the Vue instance can be acquired automatically.

Only exists if the caller was created with the option of being invoked with an args object as described in the sections above.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/layers/metadata.html b/stacks/vue/layers/metadata.html new file mode 100644 index 000000000..6990ef5d0 --- /dev/null +++ b/stacks/vue/layers/metadata.html @@ -0,0 +1,26 @@ + + + + + + Vue Metadata Layer | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/layers/models.html b/stacks/vue/layers/models.html new file mode 100644 index 000000000..3885630fb --- /dev/null +++ b/stacks/vue/layers/models.html @@ -0,0 +1,102 @@ + + + + + + Vue Model Layer | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Vue Model Layer

The model layer, generated as models.g.ts, contains a set of TypeScript interfaces that represent each client-exposed type in your data model. Each interface contains all the Properties of that type, as well as a $metadata property that references the metadata object for that type. Enums and Data Sources are also represented in the model layer.

The model layer also includes a TypeScript class for each type that can be used to easily instantiate a valid implementation of its corresponding interface. However, it is not necessary for the classes to be used, and all parts of Coalesce that interact with the model layer don't perform any instanceof checks against models - the $metadata property is used to determine type identity.

Concepts

The model layer is fairly simple - the only main concept it introduces on top of the Metadata Layer is the notion of interfaces and enums that mirror the C# types in your data model. As with the Metadata Layer, the source code of coalesce-vue is a great documentation supplement to this page.

Model

An interface describing an instance of a class type from your application's data model. All Model interfaces contain members for all the Properties of that type, as well as a $metadata property that references the metadata object for that type.

DataSource

A class-based representation of a Data Source containing properties for any of the Custom Parameters of the data source, as well as a $metadata property that references the metadata object for the data source.

Data sources are generated as concrete classes in a namespace named DataSources that is nested inside a namespace named after their parent model type. For example:

ts
import { Person } from '@/models.g'
+
+const dataSource = new Person.DataSources.NamesStartingWith;
+dataSource.startsWith = "A";
+// Provide the dataSource to an API Client or a ViewModel...

Model Functions

The following functions exported from coalesce-vue can be used with your models:

// Vue Options API
+bindToQueryString(vue: Vue, obj: {}, key: string, queryKey: string = key, parse?: (v: any) => any, mode: 'push' | 'replace' = 'replace')
+ 
+// Vue Composition API
+useBindToQueryString(obj: {}, key: string, queryKey: string = key, parse?: (v: any) => any, mode: 'push' | 'replace' = 'replace')

Binds property key of obj to query string parameter queryKey. When the object's value changes, the query string will be updated using vue-router. When the query string changes, the object's value will be updated.

The query string will be updated using either router.push or router.replace depending on the value of parameter mode.

If the query string contains a value when this is called, the object will be updated with that value immediately.

If the object being bound to has $metadata, information from that metadata will be used to serialize and parse values to and from the query string. Otherwise, String(value) will be used to serialize the value, and the parse parameter (if provided) will be used to parse the value from the query string.

ts
import { bindToQueryString } from 'coalesce-vue';
+
+// In the 'created' Vue lifecycle hook on a component:
+created() {
+    // Bind pagination information to the query string:
+    bindToQueryString(this, this.listViewModel.$params, 'pageSize', 'pageSize', v => +v);
+
+    // Assuming the component has an 'activeTab' data member:
+    bindToQueryString(this, this, 'activeTab');
+}

// Vue Options API
+bindKeyToRouteOnCreate(vue: Vue, model: Model<ModelType>, routeParamName: string = 'id', keepQuery: boolean = false)
+ 
+// Vue Composition API
+useBindKeyToRouteOnCreate(model: Model<ModelType>, routeParamName: string = 'id', keepQuery: boolean = false)

When model is created (i.e. its primary key becomes non-null), replace the current URL with one that includes uses primary key for the route parameter named by routeParamName.

The query string will not be kept when the route is changed unless true is given to keepQuery.

ts
import { bindKeyToRouteOnCreate } from 'coalesce-vue';
+
+// In the 'created' Vue lifecycle hook on a component:
+created() {
+    if (this.id) {
+        this.viewModel.$load(this.id);
+    } else {
+        bindKeyToRouteOnCreate(this, this.viewModel);
+    }
+}

Note

The route will be replaced directly via the HTML5 History API such that vue-router will not observe the change as an actual route change, preventing the current view from being recreated if a path-based key is being used on the application's <router-view> component.

Advanced Model Functions

The following functions exported from coalesce-vue can be used with your models.

Note

These functions are used to implement the higher-order layers in the Vue stack.

While you're absolutely free to use them in your own code and can rely on their interface and behavior to remain consistent, you will find that you seldom need to use them directly - that's why we've split them into their own section here in the documentation.

convertToModel(value: any, metadata: Value | ClassType): any

Given any JavaScript value value, convert it into a valid implementation of the value or type described by metadata.

For metadata describing a primitive or primitive-like value, the input will be parsed into a valid implementation of the correct JavaScript type. For example, for metadata that describes a boolean, a string "true" will return a boolean true, and ISO 8601 date strings will result in a JavaScript Date object.

For metadata describing a type, the input object will be mutated into a valid implementation of the appropriate model interface. Missing properties will be set to null, and any descendent properties of the provided object will be recursively processed with convertToModel.

If any values are encountered that are fundamentally incompatible with the requested type described by the metadata, an error will be thrown.

mapToModel(value: any, metadata: Value | ClassType): any

Performs the same operations as convertToModel, except that any objects encountered will not be mutated - instead, a new object or array will always be created.

mapToDto(value: any, metadata: Value | ClassType): any

Maps the input to a representation suitable for JSON serialization.

Will not serialize child objects or collections whose metadata includes dontSerialize. Will only recurse to a maximum depth of 3.

modelDisplay(model: Model, options?: DisplayOptions): string

Returns a string representing the model suitable for display in a user interface.

Uses the displayProp defined on the object's metadata. If no displayProp is defined, the object will be displayed as JSON. The display prop on a model can be defined in C# with [ListText].

See DisplayOptions for available options.

propDisplay(model: Model, prop: Property | string, options?: DisplayOptions): string

Returns a string representing the specified property of the given object suitable for display in a user interface.

The property can either be a string, representing one of the model's properties, or the actual Property metadata object of the property.

See DisplayOptions for available options.

valueDisplay(value: any, metadata: Value, options?: DisplayOptions): string

Returns a string representing the given value (described by the given metadata).

See DisplayOptions for available options.

DisplayOptions

The following options are available to functions in coalesce-vue that render a value or object for display:

ts
export interface DisplayOptions {
+  /** Date format options. One of:
+   * - A UTS#35 date format string (https://date-fns.org/docs/format)
+   * - An object with options for https://date-fns.org/docs/format or https://github.com/marnusw/date-fns-tz#format, including a string `format` for the format itself. If a `timeZone` option is provided per https://github.com/marnusw/date-fns-tz#format, the date being formatted will be converted to that timezone.
+   * - An object with options for https://date-fns.org/docs/formatDistance */
+  format?:
+    | string
+    | ({
+        /** A UTS#35 date format string (https://date-fns.org/docs/format) */
+        format: string;
+      } & Parameters<typeof format>[2])
+    | {
+        /** Format date with https://date-fns.org/docs/formatDistanceToNow */
+        distance: true;
+        /** Append/prepend `'in'` or `'ago'` if date is after/before now. Default `true`. */
+        addSuffix?: boolean;
+        /** Include detail smaller than one minute. Default `false`. */
+        includeSeconds?: boolean;
+      };
+
+  collection?: {
+    /** The maximum number of items to display individually.
+     * When there are more than this number of items, the count of items will be displayed instead.
+     * Default `5`.
+     * */
+    enumeratedItemsMax?: number;
+
+    /** The separator to place between enumerated items. Default `', '` */
+    enumeratedItemsSeparator?: string;
+  };
+}

Note

Dates rendered with the formatDistanceToNow function into a Vue component will not automatically be updated in realtime. If this is needed, you should use a strategy like using a key that you periodically update to force a re-render.

Time Zones

In Coalesce Vue, all DateTimeOffset-based properties, for both inputs and display-only contexts, are by default formatted into the user's computer's system time zone. This is largely just a consequence of how the JavaScript Date type works. However, this behavior can be overridden by configuring a global default timezone, or by providing a time zone name to individual usages.

Fields with a type of DateTime are agnostic to time zone and UTC offset and so are not subject to any of the following rules.

setDefaultTimeZone(timeZoneName: string | null): void

Gets or sets the default time zone used by Coalesce. The time zone should be an IANA Time Zone Database name, e.g. "America/Los_Angeles".

The time zone provided here is used in the following ways:

  • It will be used as DisplayOptions.format.timeZone if no other value was provided for this option. This is used by functions modelDisplay, propDisplay, and valueDisplay, as well as the c-display component.
  • It will be used by c-datetime-picker, used to both interpret the user input and display the selected date. This can also be set on individual component usages via the timeZone prop.
  • It will be used when serializing DateTimeOffset fields into JSON DTOs, representing the ISO 8601 date string in the specified time zone rather than in the user's computer's system time zone.

getDefaultTimeZone(): string | null

Returns the current configured default time zone. Default is null, falling back on the user's computer's system time zone.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/layers/viewmodels.html b/stacks/vue/layers/viewmodels.html new file mode 100644 index 000000000..9acbf761e --- /dev/null +++ b/stacks/vue/layers/viewmodels.html @@ -0,0 +1,81 @@ + + + + + + Vue ViewModel Layer | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Vue ViewModel Layer

The ViewModel layer, generated as viewmodels.g.ts, exports a ViewModel class for each API-backed type in your data model (Entity Models, Custom DTOs, and Services). It also exports a ListViewModel type for Entity Models and Custom DTOs.

These classes provide a wide array of functionality that is useful when interacting with your data model through a user interface. The generated ViewModels are the primary way that Coalesce is used when developing a Vue application.

ViewModels

The following members can be found on the generated Entity and Custom DTO ViewModels, exported from viewmodels.g.ts as <TypeName>ViewModel.

Model Data Properties

Each ViewModel class implements the corresponding interface from the Model Layer, meaning that the ViewModel has a data property for each Property on the model. Object-typed properties will be typed as the corresponding generated ViewModel.

Changing the value of a property will automatically flag that property as dirty. See Auto-save & Dirty Flags below for information on how property dirty flags are used.

There are a few special behaviors when assigning to different kinds of data properties on View Models as well:

Model Object Properties

  • If the object being assigned to the property is not a ViewModel instance, a new instance will be created automatically and used instead of the incoming object.
  • If the model property is a reference navigation, the corresponding foreign key property will automatically be set to the primary key of that object. If the incoming value was null, the foreign key will be set to null.
  • If deep auto-saves are enabled on the instance being assigned to, auto-save will be spread to the incoming object, and to all other objects reachable from that object.

Model Collection Properties

  • When assigning an entire array, any items in the array that are not a ViewModel instance will have an instance created for them.
  • The same rule goes for pushing items into the existing array for a model collection - a new ViewModel instance will be created and be used instead of the object(s) being pushed.

Foreign Key Properties

If the corresponding navigation property contains an object, and that object's primary key doesn't match the new foreign key value being assigned, the navigation property will be set to null.

Other Data Properties & Functions

readonly $metadata: ModelType

The metadata object from the Metadata Layer layer for the type represented by the ViewModel.

readonly $stableId: number

An immutable number that is unique among all ViewModel instances, regardless of type.

Useful for uniquely identifying instances with :key="vm.$stableId" in a Vue component, especially for instances that lack a primary key.

$primaryKey: string | number

A getter/setter property that wraps the primary key of the model. Used to interact with the primary key of any ViewModel in a polymorphic way.

$display(prop?: string | Property): string

Returns a string representation of the object, or one of its properties if specified, suitable for display.

$addChild(prop: string | ModelCollectionNavigationProperty, initialDirtyData?: {})

Creates a new instance of an item for the specified child model collection, adds it to that collection, and returns the item. If initialDirtyData is provided, it will be loaded into the new instance with $loadDirtyData().

Loading & Parameters

$load: ItemApiState;
+$load(id?: TKey) => ItemResultPromise<TModel>;

An API Caller for the /get endpoint. Accepts an optional id argument - if not provided, the ViewModel's $primaryKey is used instead. Uses the instance's $params object for the Standard Parameters.

$params: DataSourceParameters

An object containing the Standard Parameters to be used for the $load, $save, $bulkSave, and $delete API callers.

$dataSource: DataSource

Getter/setter wrapper around $params.dataSource. Takes an instance of a Data Source class generated in the Model Layer.

$includes: string | null

Getter/setter wrapper around $params.includes. See Includes String for more information.

$loadCleanData(source: {} | TModel, purgeUnsaved = false)

Loads data from the provided model into the current ViewModel, and then clears all dirty flags.

Data is loaded recursively into all related ViewModel instances, preserving existing instances whose primary keys match the incoming data.

If auto-save is enabled, only non-dirty properties are updated. This prevents user input that is pending a save from being overwritten by the response from an auto-save /save request.

If purgeUnsaved is true, items without a primary key will be dropped from collection navigation properties. This is used by the $load caller in order to fully reset the object graph with the state from the server.

$loadDirtyData(source: {} | TModel)

Same as $loadCleanData, but does not clear any existing dirty flags, nor does it clear any dirty flags that will be set while mutating the data properties of any ViewModel instance that gets loaded.

constructor(initialDirtyData?: {} | TModel | null)

Create a new instance of the ViewModel, loading the given initial data with $loadDirtyData() if provided.

Saving and Deleting

$save: ItemApiState;
+$save(overrideProps?: Partial<TModel>) => ItemResultPromise<TModel>;

An API Caller for the /save endpoint. Uses the instance's $params object for the Standard Parameters. A save operation saves only properties on the model it is called on - for deep/bulk saves, see $bulkSave.

This caller is used for both manually-triggered saves in custom code and for auto-saves. If the Rules/Validation report any errors when the caller is invoked, an error will be thrown.

overrideProps can provide properties to save that override the data properties on the ViewModel instance. This allows for manually saving a change to a property without setting the property on the ViewModel instance into a dirty state. This makes it easier to handle some scenarios where changing the value of the property may put the UI into a logically inconsistent state until the save response has been returned from the server - for example, if a change to one property affects the computed value of other properties.

When a save creates a new record and a new primary key is returned from the server, any entities attached to the current ViewModel via a collection navigation property will have their foreign keys set to the new primary key. This behavior, combined with the usage of deep auto-saves, allows for complex object graphs to be constructed even before any model in the graph has been created.

When a save is in progress, the names of properties being saved are in contained in $savingProps.

Saving behavior can be further customized with $loadResponseFromSaves and $saveMode, listed below.

$delete: ItemApiState;
+$delete() => ItemResultPromise<TModel>;

An API Caller for the /delete endpoint. Uses the instance's $params object for the Standard Parameters.

If the object was loaded as a child of a collection, it will be removed from that collection upon being deleted. Note that ViewModels currently only support tracking of a single parent collection, so if an object is programmatically added to additional collections, it will only be removed from one of them upon delete.

$loadResponseFromSaves: boolean

Default true - controls if a ViewModel will be loaded with the data from the model returned by the /save endpoint when saved with the $save API caller. There is seldom any reason to disable this.

$savingProps: ReadonlySet<string>

When $save.isLoading == true, contains the properties of the model currently being saved by $save (including auto-saves). Does not include non-dirty properties even if $saveMode == 'whole'.

This can be used to make per-property UI state changes during saves - for example, displaying progress indicators on/near individual inputs, or disabling input controls.

$saveMode: 'surgical' | 'whole'

Configures which properties of the model are sent to the server during a save or bulk save.

"surgical" (default)

By default, only dirty properties (and always the primary key) are sent to the server when performing a save.

This improves the handling of concurrent changes being made by multiple users against different fields of the same entity at the same time - specifically, it prevents a user with a stale value of some field X from overwriting a more recent value of X in the database when the user is only making changes to some other property Y and has no intention of changing X.

Save mode "surgical" doesn't help when multiple users are editing field X at the same time - if such a scenario is applicable to your application, you must implement more advanced handling of concurrency conflicts.

WARNING

Surgical saves require DTOs on the server that are capable of determining which of their properties have been set by the model binder, as surgical saves are sent from the client by entirely omitting properties from the x-www-form-urlencoded body that is sent to the server.

The Generated C# DTOs implement the necessary logic for this; however, any Custom DTOs must have this logic manually written by you, the developer. Either implement the same pattern that can be seen in the Generated C# DTOs, or do not use surgical saves with Custom DTOs.

"whole"

All serializable properties of the object are sent back to the server with every save.

$getPropDirty(propName: string): boolean

Returns true if the given property is flagged as dirty.

$setPropDirty(propName: string, dirty: boolean = true, triggerAutoSave = true)

Manually set the dirty flag of the given property to the desired state. This seldom needs to be done explicitly, as mutating a property will automatically flag it as dirty.

If dirty is true and triggerAutoSave is false, auto-save (if enabled) will not be immediately triggered for this specific flag change. Note that a future change to any other property's dirty flag will still trigger a save of all dirty properties.

$isDirty: boolean

Getter/setter that summarizes the model's property-level dirty flags. Returns true if any properties are dirty.

When set to false, all property dirty flags are cleared. When set to true, all properties are marked as dirty.

Auto-save

// Vue Options API
+$startAutoSave(vue: Vue, options: AutoSaveOptions<this> = {})
+ 
+// Vue Composition API
+$useAutoSave(options: AutoSaveOptions<this> = {})

Starts auto-saving of the instance when its savable data properties become dirty. Saves are performed with the $save API Caller (documented above) and will not be performed if the ViewModel has any validation errors - see Rules/Validation below.

ts
type AutoSaveOptions<TThis> = 
+{ 
+    /** Time, in milliseconds, to debounce saves for.  */
+    wait?: number;
+    
+    /** If true, auto-saving will also be enabled for all view models that are
+        reachable from the navigation properties & collections of the current view model. */
+    deep?: boolean;
+
+    /** Additional options to pass to the third parameter of lodash's `debounce` function. */
+    debounce?: DebounceSettings;
+
+    /** A function that will be called before autosaving that can return false to prevent a save. 
+        Only allowed if not using deep auto-saves.
+    */
+    predicate?: (viewModel: TThis) => boolean;
+}

$stopAutoSave(): void

Turns off auto-saving of the instance. Does not recursively disable auto-saves on related instances if deep was used when auto-save was enabled.

readonly $isAutoSaveEnabled: boolean

Returns true if auto-save is currently active on the instance.

Bulk saves

$bulkSave: ItemApiState;
+$bulkSave(options: BulkSaveOptions) => ItemResultPromise<TModel>;

Bulk saves save all changes to an object graph in one API call and one database transaction. This includes creation, updates, and deletions of entities.

To use bulk saves, you can work with your ViewModel instances on the client much in the same way you would on the server with Entity Framework. Assign objects to reference navigation properties and modify scalar values to perform creates and updates. To perform deletions, you must call model.$remove() on the ViewModel you want to remove, similar how you would call DbSet<>.Remove(model) on the server.

If the client-side Rules/Validation report any errors for any of the models being saved in the operation, an error will be thrown.

On the server, each affected entity is handled through the same standard mechanisms as are used by individual saves or deletes (Behaviors, Data Sources, and Security Attributes), but with a bit of sugar on top:

  • All operations are wrapped in a single database transaction that is rolled back if any individual operation fails.
  • Foreign keys will be fixed up as new items are created, allowing a parent and child record to be created at the same time even when the client has no foreign key to link the two together.

For the response to a bulk save, the server will load and return the root ViewModel that $bulkSave was called upon, using the instance's $params object for the Standard Parameters.

ts
export interface BulkSaveOptions {
+  /** A predicate that will be applied to each modified model
+   * to determine if it should be included in the bulk save operation.
+   *
+   * The predicate is applied before validation (`$hasError`), allowing
+   * it to be used to skip over entities that have client validation errors
+   * that would otherwise cause the entire bulk save operation to fail.
+   * */
+  predicate?: (viewModel: ViewModel, action: "save" | "delete") => boolean;
+}

$remove(): void

Removes the item from its parent collection (if it is in a collection), and marks the item for deletion in the next bulk save.

readonly $isRemoved: boolean

Returns true if the instance was previously removed by calling $remove().

Rules/Validation

$addRule(prop: string | Property, identifier: string, rule: (val: any) => true | string)

Add a custom validation rule to the ViewModel for the specified property. identifier should be a short, unique slug that describes the rule; it is not displayed in the UI, but is used if you wish to later remove the rule with $removeRule().

The function you provide should take a single argument that contains the current value of the property, and should either return true to indicate that the validation rule has succeeded, or a string that will be displayed as an error message to the user.

Any failing validation rules on a ViewModel will prevent that ViewModel's $save caller from being invoked.

$removeRule(prop: string | Property, identifier: string)

Remove a validation rule from the ViewModel for the specified property and rule identifier.

This can be used to remove either a rule that was provided by the generated Metadata Layer, or a custom rule that was added by $addRule. Reference your generated metadata file metadata.g.ts to see any generated rules and the identifiers they use.

$getRules(prop: string | Property): ((val: any) => string | true)[]

Returns an array of active rule functions for the specified property, or undefined if the property has no active validation rules.

$getErrors(prop?: string | Property): Generator<string>

Returns a generator that provides all error messages for either a specific property (if provided) or the entire model (if no prop argument is provided).

TIP

You can obtain an array from a generator with Array.from(vm.$getErrors()) or [...vm.$getErrors()]

readonly $hasError: boolean

Indicates if any properties have validation errors.

Generated Members

API Callers

For each of the instance Methods of the type, an API Caller will be generated.

addTo*() Functions

For each collection navigation property, a method is generated that will create a new instance of the ViewModel for the collected type, add it to the collection, and then return the new object.

Many-to-many helper collections

For each collection navigation property annotated with [ManyToMany], a getter-only property is generated that returns a collection of the object on the far side of the many-to-many relationship. Nulls are filtered from this collection.

ListViewModels

The following members can be found on the generated ListViewModels, exported from viewmodels.g.ts as *TypeName*ListViewModel.

Data Properties

readonly $items: T[]

Collection holding the results of the last successful invocation of the $load API Caller.

Parameters & API Callers

$params: ListParameters

An object containing the Standard Parameters to be used for the $load and $count API callers.

$dataSource: DataSource

Getter/setter wrapper around $params.dataSource. Takes an instance of a Data Source class generated in the Model Layer.

$includes: string | null

Getter/setter wrapper around $params.includes. See Includes String for more information.

$load: ListApiState;
+$load() => ListResultPromise<TModel>

An API Caller for the /list endpoint. Uses the instance's $params object for the Standard Parameters.

Results are available in the $items property. The result property of the $load API Caller contains the raw results and is not recommended for use in general development - $items should always be preferred.

$count: ItemApiState;
+$count() => ItemResultPromise<number>

An API Caller for the /count endpoint. Uses the instance's $params object for the Standard Parameters.

The result is available in $count.result - this API Caller does not interact with other properties on the ListViewModel like $pageSize or $pageCount.

readonly $hasPreviousPage: boolean 
+readonly $hasNextPage: boolean

Properties which indicate if $page can be decremented or incremented, respectively. $pageCount and $page are used to make this determination.

$previousPage(): void 
+$nextPage(): void

Methods that will decrement or increment $page, respectively. Each does nothing if there is no previous or next page as returned by $hasPreviousPage and $hasNextPage.

$page: number

Getter/setter wrapper for $params.page. Controls the page that will be requested on the next invocation of $load.

$pageSize: number

Getter/setter wrapper for $params.pageSize. Controls the page that will be requested on the next invocation of $load.

readonly $pageCount: number

Shorthand for $load.pageCount - returns the page count reported by the last successful invocation of $load.

Auto-Load

// Vue Options API
+$startAutoLoad(vue: Vue, options: AutoLoadOptions<this> = {})
+ 
+// Vue Composition API
+$useAutoLoad(options: AutoLoadOptions<this> = {})

Starts auto-loading of the list as changes to its parameters occur. Loads are performed with the $load API Caller.

ts
type AutoLoadOptions<TThis> =
+{ 
+    /** Time, in milliseconds, to debounce loads for.  */
+    wait?: number;
+
+    /** Additional options to pass to the third parameter of lodash's `debounce` function. */
+    debounce?: DebounceSettings;
+
+    /** A function that will be called before loading that can return false to prevent a load. */
+    predicate?: (viewModel: TThis) => boolean;
+}

$stopAutoLoad()

Manually turns off auto-loading of the instance.

Auto-save

// Vue Options API
+$startAutoSave(vue: Vue, options: AutoSaveOptions<this> = {})
+ 
+// Vue Composition API
+$useAutoSave(options: AutoSaveOptions<this> = {})

Enables auto-save for the items in the list, propagating to new items as they're added or loaded. See ViewModel auto-save documentation for more details.

$stopAutoSave(): void

Turns off auto-saving of the items in the list, and turns of propagation of auto-save to any future items if auto-save was previously turned on for the list. Only affects items that are currently in the list's $items.

readonly $isAutoSaveEnabled: boolean

Returns true if auto-save is currently active on the instance.

Generated Members

API Callers

For each of the static Methods on the type, an API Caller will be created.

Service ViewModels

The following members can be found on the generated Service ViewModels, exported from viewmodels.g.ts as <ServiceName>ViewModel.

Generated Members

API Callers

For each method of the Service, an API Caller will be created.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/overview.html b/stacks/vue/overview.html new file mode 100644 index 000000000..b1301e2e4 --- /dev/null +++ b/stacks/vue/overview.html @@ -0,0 +1,26 @@ + + + + + + Vue Overview | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Vue Overview

The Vue stack for Coalesce has been designed from the ground up to be used to build modern web applications using current technologies like Vite, ES Modules, and more. It enables you to use all of the features of Vue.js, including building a SPA, and the ability to use modern component frameworks like Vuetify.

Getting Started

Check out Getting Started with Vue to learn how to get a new Coalesce Vue project up and running.

TypeScript Layers

The generated code for the Vue stack all builds on the coalesce-vue NPM package which contains most of the core functionality of the Vue stack. Its version should generally be kept in sync with the IntelliTect.Coalesce NuGet packages in your project.

Both the generated code and coalesce-vue are split into four layers, with each layer building on the layers underneath. From the bottom, these layers are:

Metadata Layer

The metadata layer, generated as metadata.g.ts, contains information about the types, properties, methods, and other components of your data model. Because Vue applications are typically compiled into a set of static assets, it is necessary for the frontend code to have a representation of your data model as an analog to the ReflectionRepository that is available at runtime in your .NET app.

Read more about the Metadata layer

Model Layer

The model layer, generated as models.g.ts, contains a set of TypeScript interfaces that represent each client-exposed type in your data model. Each interface contains all the Properties of that type, as well as a $metadata property that references the metadata object for that type. Enums and Data Sources are also represented in the model layer.

Read more about the Model layer

API Client Layer

The API client layer, generated as api-clients.g.ts, exports a class for each API controller that was generated for your data model. These classes are stateless and provide one method for each API endpoint. This includes both the standard set of endpoints created for Entity Models and Custom DTOs, as well as any custom Methods on the aforementioned types, as well as any methods on your Services.

Read more about the API Client layer

ViewModel Layer

The ViewModel layer, generated as viewmodels.g.ts, exports a ViewModel class for each API-backed type in your data model (Entity Models, Custom DTOs, and Services). It also exports a ListViewModel type for Entity Models and Custom DTOs.

These classes provide a wide array of functionality that is useful when interacting with your data model through a user interface. The generated ViewModels are the primary way that Coalesce is used when developing a Vue application.

Read more about the ViewModel layer

Vue Components

The Vue stack for Coalesce provides a set of components based on Vuetify, packaged up in an NPM package coalesce-vue-vuetify2 or coalesce-vue-vuetify3. These components are driven primarily by the Metadata Layer, and include both low level input and display components like c-input and c-display that are highly reusable in the custom pages you'll build in your application, as well as high-level components like c-admin-table-page and c-admin-editor-page that constitute entire pages.

Read more about the Vuetify Components here.

Admin Views

The Vue.js stack for Coalesce provides some high level components that provide functionality of whole pages like c-admin-table-page and c-admin-editor-page.

The template described in Getting Started with Vue comes with routes already in place for these page-level components. For example, /admin/Person for a table, /admin/Person/edit to create a new Person, and /admin/Person/edit/:id to edit a Person.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/stacks/vue/vue2-to-vue3.html b/stacks/vue/vue2-to-vue3.html new file mode 100644 index 000000000..3e00eb72a --- /dev/null +++ b/stacks/vue/vue2-to-vue3.html @@ -0,0 +1,202 @@ + + + + + + Vue 2 to Vue 3 | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Vue 2 to Vue 3

If you're already experienced with Vue 2 but are new to Vue 3, or if you're migrating an existing Vue 2 app to Vue 3, you should first read through the official migration guide.

Vuetify also offers a migration guide to upgrade from Vuetify 2 to Vuetify 3.

If you're new to Vue entirely, check out the rest of Vue docs and pick your learning path.

Coalesce Upgrade Steps

The changes specific to Coalesce when migrating from Vue2 to Vue3 are pretty minimal. Most of your work will be in following the Vue 3 Migration Guide and the Vuetify 3 Migration Guide.

The table below contains the Coalesce-specific changes when migrating to Vue 3. However, the easiest migration path may be to disregard the table below and instead, instantiate the Coalesce Vue template or look at it on GitHub and compare individual files between your project and the template side by side and ingest the changes that you observe.

LocationOld (Vue 2)New (Vue 3)

package.json

json
{
+  "dependencies": {
+    "coalesce-vue-vuetify2": "x"
+  }
+}
json
{
+  "dependencies": {
+    "coalesce-vue-vuetify3": "x"
+  }
+}

vite.config.ts

ts
import { CoalesceVuetifyResolver } from "coalesce-vue-vuetify2/lib/build"
ts
import { CoalesceVuetifyResolver } from "coalesce-vue-vuetify3/build"
+
+// Custom SASS options and `optimizeDeps` configuration can be removed
+// since Vuetify3 no longer uses deprecated sass features,
+// and pre-bundling styles no longer has appreciable benefit.

main.ts

ts
import "coalesce-vue-vuetify2/dist/coalesce-vue-vuetify.css"
+
+// Either of these:
+import CoalesceVuetify from 'coalesce-vue-vuetify2/lib'
+import CoalesceVuetify from 'coalesce-vue-vuetify2'
+
+Vue.use(CoalesceVuetify, {
+  metadata: $metadata,
+});
ts
import "coalesce-vue-vuetify3/styles.css"
+
+
+import { createCoalesceVuetify } from "coalesce-vue-vuetify3";
+
+
+const coalesceVuetify = createCoalesceVuetify({
+  metadata: $metadata,
+});
+app.use(coalesceVuetify);

router.ts

ts
// Either of these:
+import { CAdminTablePage, CAdminEditorPage } from 'coalesce-vue-vuetify2/lib';
+import { CAdminTablePage, CAdminEditorPage } from 'coalesce-vue-vuetify2';
ts

+import { CAdminEditorPage, CAdminTablePage } from "coalesce-vue-vuetify3";

Vitest/Jest tests

If you had a global test setup file performing Vue configuration, you can likely remove it entirely, or at least remove the parts that configure Vue. Vue3 does not operate on global configuration like Vue2 did.

See test-utils.ts and HelloWorld.spec.ts in the template for examples of Vue3 component testing.

From Class Components to <script setup>

The components in the Coalesce template for Vue 3 have switched from vue-class-component to Vue Composition API with <script setup>, the official recommendation for building full Vue 3 applications.

If you're used to writing components in Vue 2 with vue-class-component and vue-property-decorator, you can use this table of comparisons as a quick reference of what the equivalent features are using <script setup> and Vue Composition API. That said, this is not a replacement for learning and understanding the composition API. You should read the Composition API FAQ as well as the Reactivity Fundamentals documentation (make sure to set the API preference in the top left to Composition!).

If you'd like to continue using class components with Vue 3 (e.g. upgrading an existing project where rewriting all components is not feasible), you can try switching to vue-facing-decorator.

Note

The examples below assume that unplugin-auto-import is being used (included in the Coalesce Vue3 template), eliminating the need to manually import common Vue Composition API functions.

FeatureClass ComponentScript Setup

Coalesce ViewModel and ListViewModel usage

vue
<script lang="ts">
+import { Vue, Component } from "vue-property-decorator";
+import { PersonViewModel, PersonListViewModel } from "@/viewmodels.g";
+
+@Component({})
+export default class MyComponent extends Vue {
+  public person = new PersonViewModel();
+  public list = new PersonListViewModel();
+
+  async created() {
+    await person.$load();
+    await list.$load();
+
+    person.$startAutoSave(this);
+    list.$startAutoLoad(this);
+  }
+}
+</script>
vue
<script lang="ts" setup>
+import { PersonViewModel, PersonListViewModel } from "@/viewmodels.g";
+
+const person = new PersonViewModel();
+const list = new PersonListViewModel();
+
+person.$useAutoSave();
+list.$useAutoLoad();
+
+// If you need to await an async operation during component creation, 
+// use an IIFE so that the component mount is not delayed.
+(async function created() {
+  await person.$load();
+  await list.$load();
+})();
+</script>

@Prop, @Watch

vue
<script lang="ts">
+import { Vue, Component, Prop, Watch } from "vue-property-decorator";
+
+@Component({})
+export default class MyComponent extends Vue {
+  @Prop({ default: "Student" })
+  label!: string;
+
+  @Prop({ required: true })
+  student!: ApplicationUserViewModel;
+
+  @Watch("label")
+  labelChanged(newVal, oldVal) {
+    console.log(`label changed. new:${newVal}, old:${oldVal}`)
+  }
+}
+</script>
vue
<script lang="ts" setup>
+const props = withDefaults(defineProps<{
+  label?: string,
+  student?: ApplicationUserViewModel
+}>(), { label: 'Student' })
+
+watch(
+  () => props.label,
+  (newVal, oldVal) => {
+    console.log(`label changed. new:${newVal}, old:${oldVal}`);
+  }
+);
+</script>

Reactive data

vue
<script lang="ts">
+import { Vue, Component } from "vue-property-decorator";
+import { PersonViewModel } from "@/viewmodels.g";
+
+@Component({})
+export default class MyComponent extends Vue {
+  public person = new PersonViewModel();
+
+  public checked = false;
+
+  public items = [
+    { name: "Foo", checked: false, },
+    { name: "Bar", checked: true, }
+  ]
+}
+</script>
vue
<script lang="ts" setup>
+import { PersonViewModel } from "@/viewmodels.g";
+
+// Properties on coalesce-generated ViewModels have built in reactivity 
+// and don't need to be wrapped ref/reactive unless you're going to replace 
+// the entire top level object with a different instance.
+const person = new PersonViewModel();
+
+const checked = ref(false);
+
+const items = reactive([
+  { name: "Foo", checked: false, },
+  { name: "Bar", checked: true, }
+])
+</script>

Computed values

vue
<script lang="ts">
+import { Vue, Component } from "vue-property-decorator";
+import { PersonViewModel } from "@/viewmodels.g";
+
+@Component({})
+export default class MyComponent extends Vue {
+  public person = new PersonViewModel()
+
+  get fullName() {
+    return `${person.firstName} ${person.lastName}`
+  }
+}
+</script>
vue
<script lang="ts" setup>
+import { PersonViewModel } from "@/viewmodels.g";
+
+const person = new PersonViewModel();
+
+const fullName = computed(() => `${person.firstName} ${person.lastName}`)
+</script>

$emit, methods

vue
<template>
+  <input
+    :value="value"
+    @input="inputChanged($event.target.value)"
+  />
+</template>
+
+<script lang="ts">
+import { Vue, Component } from "vue-property-decorator";
+
+@Component({})
+export default class MyComponent extends Vue {
+  @Prop()
+  value!: string;
+
+  inputChanged(v: string) {
+    this.$emit('update:input', v)
+  }
+}
+</script>
vue
<template>
+  <input
+    :value="modelValue"
+    @input="inputChanged(($event.target as HTMLInputElement).value)"
+  />
+</template>
+
+<script lang="ts" setup>
+defineProps<{ modelValue: string | null }>();
+
+// This may seem tedious, but it enables full Typescript intellisense!
+const emit = defineEmits<{
+  (e: "update:modelValue", value: string | null): void;
+}>();
+
+function inputChanged(v: string) {
+  emit('update:modelValue', v)
+}
+</script>

Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/topics/audit-logging.html b/topics/audit-logging.html new file mode 100644 index 000000000..87ca09a6d --- /dev/null +++ b/topics/audit-logging.html @@ -0,0 +1,126 @@ + + + + + + Audit Logging | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Audit Logging

Keeping a history of all (or most) of the changes that are made to records in your database can be invaluable, both for non-repudiation (i.e. proving what happened and who did it), and for troubleshooting or debugging.

Coalesce provides a package IntelliTect.Coalesce.AuditLogging that adds an easy way to inject this kind of audit logging into your EF Core DbContext. It also includes an out-of-the-box view c-admin-audit-log-page that enables browsing of this data on the frontend.

Setup

In this setup process, we're going to add an additional Coalesce Nuget package, define a custom entity to hold our audit logs, install the audit logging extension into our DbContext, and add a pre-made interface on the frontend to view our logs.

1. Add the NuGet package

Add a reference to the Nuget package IntelliTect.Coalesce.AuditLogging to your data project:

xml
<ItemGroup>
+  <PackageReference Include="IntelliTect.Coalesce.Vue" Version="$(CoalesceVersion)" />
+  <PackageReference Include="IntelliTect.Coalesce.AuditLogging" Version="$(CoalesceVersion)" />
+</ItemGroup>

2. Define the log entity

Define the entity type that will hold the audit records in your database:

c#
using IntelliTect.Coalesce.AuditLogging;
+
+[Read(Roles = "Administrator")]
+public class AuditLog : DefaultAuditLog
+{
+    public string? UserId { get; set; }
+    public AppUser? User { get; set; }
+
+    // Other custom props as desired
+}

This entity only needs to implement IAuditLog, but a default implementation of this interface DefaultAuditLog is provided for your convenience. DefaultAuditLog contains additional properties ClientIp, Referrer, and Endpoint for recording information about the HTTP request (if available), and also comes pre-configured for security with Create, Edit, and Delete APIs disabled.

You should further augment this type with any additional properties that you would like to track on each change record. A property to track the user who performed the change should be added, since it is not provided by the default implementation so that you can declare it yourself with the correct type for the foreign key and navigation property.

You should also apply security to restrict reading of these records to only the most privileged users with a Read Attribute (as in the example above) and/or a custom Default Data Source.

3. Configure your DbContext

On your DbContext, implement the IAuditLogDbContext<AuditLog> interface using the class you just created as the type parameter. Then register the Coalesce audit logging extension in your DbContext's OnConfiguring method so that saves will be intercepted and audit log entries created.

c#
[Coalesce]
+public class AppDbContext : DbContext, IAuditLogDbContext<AuditLog>
+{
+    public DbSet<AuditLog> AuditLogs { get; set; }
+    public DbSet<AuditLogProperty> AuditLogProperties { get; set; }
+
+    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+    {
+        optionsBuilder.UseCoalesceAuditLogging<AuditLog>(x => x
+            .WithAugmentation<OperationContext>()
+        );
+    }
+}

You could also perform this setup in your web project when calling .AddDbContext().

The above code also contains a reference to a class OperationContext. This is the service that will allow you to populate additional custom properties on your audit entries. You'll want to define it as follows:

c#
public class OperationContext : DefaultAuditOperationContext<AuditLog>
+{
+    // Inject any additional desired services in the constructor:
+    public OperationContext(IHttpContextAccessor accessor) : base(accessor) { }
+
+    public override void Populate(AuditLog auditEntry, EntityEntry changedEntity)
+    {
+        base.Populate(auditEntry, changedEntity);
+
+        // Adjust as needed to retrieve your UserId from the ClaimsPrincipal.
+        auditEntry.UserId = User.GetUserId();
+    }
+}

When you're inheriting from DefaultAuditLog for your IAuditLog implementation, you'll want to similarly inherit from DefaultAuditOperationContext<> for your operation context. It will take care of populating the HTTP request tracking fields on the AuditLog record. If you want a totally custom implementation, you only need to implement the IAuditOperationContext<TAuditLog> interface.

The operation context class passed to WithAugmentation will be injected from the application service provider if available; otherwise, a new instance will be constructed using dependencies from the application service provider. To make an injected dependency optional, make the constructor parameter nullable with a default value of null, or create alternate constructors.

4. Add the UI

For Vue applications, the c-admin-audit-log-page component provides an out-of-the-box user interface for browsing through audit logs. Simply define the following route in your application's router:

ts
import { CAdminAuditLogPage } from 'coalesce-vue-vuetify3';
+
+{
+  path: '/admin/audit-logs',
+  component: CAdminAuditLogPage,
+  props: { type: 'AuditLog' }
+}

Configuration

Suppression

You can turn audit logging on or off for individual operations by implementing the SuppressAudit property on your DbContext. For example, implement it as an auto-property as follows and then set it to true in application code when desired:

c#
[Coalesce]
+public class AppDbContext : DbContext, IAuditLogDbContext<AuditLog>
+{
+    ...
+    public bool SuppressAudit { get; set; }
+}

Exclusions & Formatting

Coalesce's audit logging is built on top of Entity Framework Plus and can be configured using all of its configuration, including includes/excludes and custom property formatting.

Coalesce will not use EF Plus's AuditManager.DefaultConfiguration global singleton instance. You must use Coalesce's configuration extensions which allow for more targeted configuration per context that does not rely on a global static singleton. For example:

c#
public class AppDbContext : DbContext, IAuditLogDbContext<AuditLog>
+{
+    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+    {
+        optionsBuilder.UseCoalesceAuditLogging<AuditLog>(x => x
+            .WithAugmentation<OperationContext>()
+            .ConfigureAudit(c => c
+                .Exclude<DataProtectionKey>()
+                .ExcludeProperty<TrackingBase>(x => new { x.CreatedById, x.CreatedOn, x.ModifiedById, x.ModifiedOn })
+                .FormatType<DateTimeOffset>(d => d.ToTimeZone("America/Los_Angeles").ToString())
+                .Format<Image>(x => x.Content, x => $"{Convert.ToHexString(SHA1.HashData(x))}, {x.Length} bytes")
+            )
+        );
+    }
+}

Property Descriptions

The AuditLogProperty children of your IAuditLog implementation have two properties OldValueDescription and NewValueDescription that can be used to hold a description of the old and new values. By default, Coalesce will populate the descriptions of foreign key properties with the List Text of the referenced principal entity. This greatly improves the usability of the audit logs, which would otherwise only show meaningless numbers or GUIDs for foreign keys that changed.

This feature will load principal entities into the DbContext if they are not already loaded, which could inflict subtle differences in application functionality in rare edge cases if your application is making assumptions about navigation properties not being loaded. Typically though, this will not be an issue and will not lead unintentional information disclosure to clients as long as IncludeTrees are used correctly.

This feature may be disabled by calling .WithPropertyDescriptions(PropertyDescriptionMode.None) inside your call to .UseCoalesceAuditLogging(...) in your DbContext configuration. You may also populate these descriptions in your IAuditOperationContext implementation that was provided to .WithAugmentation<T>().

Merging

When using a supported database provider (currently only SQL Server), audit records for changes to the same entity will be merged together when the change is identical in all aspects to the previous audit record for that entity, with the sole exception of the old/new property values.

In other words, if the same user is making repeated changes to the same property on the same entity from the same page, then those changes will merge together into one audit record.

This merging only happens together if the existing audit record is recent; the default cutoff for this is 30 seconds, but can be configured with .WithMergeWindow(TimeSpan.FromSeconds(15)) when calling UseCoalesceAuditLogging. It can also be turned off by setting this value to TimeSpan.Zero. The merging logic respects all custom properties you add to your IAuditLog implementation, requiring their values to match between the existing and new audit records for a merge to occur.

Caveats

Only changes that are tracked by the DbContext's ChangeTracker can be audited. Changes that are made with raw SQL, or changes that are made with bulk update functions like ExecuteUpdate or ExecuteDelete will not be audited using this package.

Audit Stamping

A lightweight alternative or addition to full audit logging is audit stamping - the process of setting fields like CreatedBy or ModifiedOn on each changed entity. This cannot record a history of exact changes, but can at least record the age of an entity and how recently it changed.

Coalesce offers a simple mechanism to register an Entity Framework save interceptor to perform this kind of action (this does NOT require the IntelliTect.Coalesce.AuditLogging package). This mechanism operations on all saves that go through Entity Framework, eliminating the need to perform this manually in individual Behaviors, Services, and Custom Methods:

c#
services.AddDbContext<AppDbContext>(options => options
+    .UseSqlServer(connectionString) // (or other provider)
+    .UseStamping<TrackingBase>((entity, user) => entity.SetTracking(user))
+);

In the above example, TrackingBase is an interface or class that you would write as part of your application that defines the properties and mechanisms for performing the tracking operation. For example:

c#
public abstract class TrackingBase
+{
+    [Read, Display(Order = 1000)]
+    public ApplicationUser CreatedBy { get; set; }
+    
+    [Read, Display(Order = 1001)]
+    public string? CreatedById { get; set; }
+    
+    [Read, Display(Order = 1002)]
+    public DateTimeOffset CreatedOn { get; set; }
+
+
+    [Read, Display(Order = 1003)]
+    public ApplicationUser ModifiedBy { get; set; }
+    
+    [Read, Display(Order = 1004)]
+    public string? ModifiedById { get; set; }
+    
+    [Read, Display(Order = 1005)]
+    public DateTimeOffset ModifiedOn { get; set; }
+
+
+    public void SetTracking(ClaimsPrincipal? user) 
+        => SetTracking(user?.GetApplicationUserId());
+    
+    public void SetTracking(int? userId)
+    {
+        if (this.CreatedById == null)
+        {
+            this.CreatedById = userId;
+            this.CreatedOn = DateTimeOffset.Now;
+        }
+
+        this.ModifiedById = userId;
+        this.ModifiedOn = DateTimeOffset.Now;
+    }
+}

The overload UseStamping<TStampable> will provide the ClaimsPrincipal from the current HTTP request if present, defaulting to null if an operation occurs outside an HTTP request (e.g. a background job). The overloads UseStamping<TStampable, TService> and UseStamping<TStampable, TService1, TService2> can be used to inject services into the operation. If more than two services are needed, you should wrap those dependencies up into an additional services that takes them as dependencies.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/topics/coalesce-json.html b/topics/coalesce-json.html new file mode 100644 index 000000000..963ca373d --- /dev/null +++ b/topics/coalesce-json.html @@ -0,0 +1,94 @@ + + + + + + Code Generation Configuration | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Code Generation Configuration

In Coalesce, all configuration of the code generation is done in a JSON file. This file is typically named coalesce.json and is typically placed in the solution root.

File Resolution

When the code generation is run by invoking dotnet coalesce, Coalesce will try to find a configuration file via the following means:

  1. If an argument is specified on the command line, it will be used as the location of the file. E.g. dotnet coalesce C:/Projects/MyProject/config.json
  2. If no argument is given, Coalesce will try to use a file in the working directory named coalesce.json
  3. If no file is found in the working directory, Coalesce will crawl up the directory tree from the working directory until a file named coalesce.json is found. If such a file is never found, an error will be thrown.

Contents

A full example of a coalesce.json file, along with an explanation of each property, is as follows:

js
{
+    "webProject": {
+        // Required: Path to the csproj of the web project. Path is relative to location of this coalesce.json file.
+        "projectFile": "src/Coalesce.Web/Coalesce.Web.csproj",
+
+        // Optional: Framework to use when evaluating & building dependencies.
+        // Not needed if your project only specifies a single framework - only required for multi-targeting projects.
+        "framework": "netcoreapp2.0",
+
+        // Optional: Build configuration to use when evaluating & building dependencies.
+        // Defaults to "Debug".
+        "configuration": "Debug",
+
+        // Optional: Override the namespace prefix for generated C# code.
+        // Defaults to MSBuild's `$(RootNamespace)` for the project.
+        "rootNamespace": "MyCompany.Coalesce.Web",
+    },
+
+    "dataProject": {
+        // Required: Path to the csproj of the data project. Path is relative to location of this coalesce.json file.
+        "projectFile": "src/Coalesce.Domain/Coalesce.Domain.csproj",
+
+        // Optional: Framework to use when evaluating & building dependencies.
+        // Not needed if your project only specifies a single framework - only required for multi-targeting projects.
+        "framework": "netstandard2.0",
+
+        // Optional: Build configuration to use when evaluating & building dependencies.
+        // Defaults to "Release".
+        "configuration": "Debug",
+    },
+
+    // The name of the root generator to use. Defaults to "Knockout".
+    // Available values are "Vue" and "Knockout".
+    "rootGenerator": "Vue",
+            
+    // If set, specifies a list of whitelisted root type names that will restrict
+    // which types Coalesce will use for code generation. 
+    // Root types are those that must be annotated with [Coalesce].
+    // Useful if want to segment a single data project into multiple web projects, 
+    // or into different areas/directories within a single web project.
+    "rootTypesWhitelist": [
+        "MyDbContext", "MyCustomDto"
+    ],
+
+    "generatorConfig": {
+        // A set of objects keyed by generator name.
+        // Generator names may optionally be qualified by their full namespace.
+        // All generators are listed when running 'dotnet coalesce' with '--verbosity debug'.
+        // For example, "Views" or "IntelliTect.Coalesce.CodeGeneration.Knockout.Generators.Views".
+        "GeneratorName": {
+            // Optional: true if the generator should be disabled.
+            "disabled": true,
+            // Optional: Configures a path relative to the default output path for the generator
+            // where that generator's output should be placed instead.
+            "targetDirectory": "../DifferentFolder"
+        },
+        // Indentation for generated C# is configurable by type (API controllers, DTO classes and regular View controllers)
+        // It defaults to 4 spaces
+        "ApiController": {
+            "indentationSize": 2 
+        },
+        "ClassDto": {
+            "indentationSize": 2 
+        },
+        "ViewController" : {
+            "indentationSize": 2
+        }
+    }
+}

Additional CLI Options

There are a couple of extra options which are only available as CLI parameters to dotnet coalesce. These options do not affect the behavior of the code generation - only the behavior of the CLI itself.

--debug

When this flag is specified when running dotnet coalesce, Coalesce will wait up to 60 seconds for a debugger to be attached to its process before starting code generation.

-v|--verbosity <level>

Set the verbosity of the output. Options are trace, debug, information, warning, error, critical, and none.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/topics/security.html b/topics/security.html new file mode 100644 index 000000000..bf2ab2715 --- /dev/null +++ b/topics/security.html @@ -0,0 +1,316 @@ + + + + + + Security | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Security

This page is a comprehensive overview of all the techniques that can be used in a Coalesce application to restrict the capabilities of API endpoints that Coalesce generates.

The following table is a quick reference of scenarios you might encounter and how you might handle them. If you're unfamiliar with these techniques, though, then you are encouraged to read through this page to get a deeper understanding of what's available before selecting a solution.

FeatureRestrictionTechnique

Entity Reads: /get,
/list,
/count

Disable

[Read(DenyAll)]

Roles

[Read("RoleName")]

Prevent auto-include

  • Omit base call in Data Source GetQuery override.
  • [Read(NoAutoInclude = true)] on properties or types.

Any custom code:

  • Query Predicates
  • Filtered Includes
  • Conditional Includes
  • Sort/search/filter overrides

Custom Default Data Source

Entity Mutations: /save,
/bulkSave,
/delete

Disable

[Create(DenyAll)] [Edit(DenyAll)] [Delete(DenyAll)]

Roles

[Create("Role")] [Edit("Role")] [Delete("Role")]

Restrict target records (edit/delete)

Custom Default Data Source

Static Validation

Validation attributes

Any custom code:

  • Security
  • Validation

Custom Behaviors

Methods and Services

Disable

N/A - explicit opt-in required via [Coalesce]

Roles

[Execute("RoleName")]

Static Validation

Validation attributes

Restrict Targets (only instance methods)

Other

Write custom logic in the method.

Properties
(All input and output for Entity CRUD, Methods, and Services)

Globally Exclude
Roles

Property Security Attributes

Read-only

Init-only (write-once)

init setter

Custom security

Endpoint Security

Coalesce generates API endpoints by traversing your data model's classes, starting from types annotated with [Coalesce]. This usually includes your DbContext class, as well as any Service classes or interfaces.

Classes can be hidden from Coalesce entirely by annotating them with [InternalUse], preventing generation of API endpoints for that class, as well as preventing properties of that type from being exposed.

DbSet<> properties on your DbContext class can also be annotated with [InternalUse], causing that type to be treated by Coalesce like an External Type rather than an Entity, once again preventing generation of API endpoints but without preventing properties of that type from being exposed.

Class Security Attributes

For each of your Entities and Custom DTOs, Coalesce generates a set of CRUD API endpoints (/get, /list, /count, /save, /bulkSave, and /delete).

The default behavior is that all endpoints require an authenticated user (anonymous users are rejected).

These endpoints can be secured by placing any or all of the [Read], [Create], [Edit], and [Delete] attributes on the the class. Each attribute can specify required roles for that action, or open that action to anonymous, unauthenticated users, or disable the endpoint entirely.

This security is applied to the generated controllers. The [Read] attribute on a class does not affect instances of that class when those instances are present as child properties of other types, since in those scenarios the data will be coming from a different endpoint on a different controller.

EndpointsGoverning Attributes

/get, /list, /count

c#
[ReadAttribute]

/save

c#
[CreateAttribute] // Affects saves of new entities
+[EditAttribute]   // Affects saves of existing entities

/delete

c#
[DeleteAttribute]

/bulkSave

c#
// Read permission required for the root entity:
+[ReadAttribute]
+
+// Control of each entity affected by the bulk save:
+[CreateAttribute]
+[EditAttribute]
+[DeleteAttribute]

Here are some examples of applying security attributes to an entity class. If a particular action doesn't need to be restricted, you can omit that attribute, but this example shows usages of all four:

c#
// Allow read access by unauthenticated, anonymous users:
+[Read(SecurityPermissionLevels.AllowAll)]
+// Allow creation of new entities by the Admin and HR roles (params string[] style):
+[Create("Admin", "HR")]
+// Allow editing of existing Employee entities by users with the Admin or HR roles (CSV style):
+[Edit("Admin,HR")]
+// Prohibit deletion of Employee entities
+[Delete(SecurityPermissionLevels.DenyAll)]
+public class Employee
+{
+    public int EmployeeId { get; set; }
+}

Method Security Attributes

To secure the endpoints generated for your Custom Methods and Services, the [Execute] attribute can be used to specify a set of required roles for that endpoint, or to open that endpoint to anonymous users.

The default behavior is that all endpoints require an authenticated user (anonymous users are rejected).

For example:

c#
public class Employee
+{
+    public int EmployeeId { get; set; }
+
+    [Coalesce, Execute("Payroll,HR")]
+    public void GiveRaise(int centsPerHour) {
+        // Only Payroll and HR users can call this method
+    }
+
+    [Coalesce, Execute(SecurityPermissionLevels.AllowAll)]
+    public void SendMessage(string message) {
+        // Anyone (even anonymous, unauthenticated users) can call this method.
+    }
+}

Property/Column Security

Security applied via attributes to properties in Coalesce affects all usages of that property across all Coalesce-generated APIs. This includes usages of that property on types that occur as children of other types, which is a spot where class-level or endpoint-level security generally does not apply. These attributes can be placed on the properties on your Entities and External Types to apply role-based restrictions to that property.

  • ReadAttribute limits the roles that can read values from that property in responses from the server.
  • EditAttribute limits the roles that can write values to that property in requests made to the server.
  • RestrictAttribute registers an implementation of IPropertyRestriction that allows for writing custom code to implement these restrictions.

This security is executed and enforced by the mapping that occurs in the generated DTOs, meaning it affects both entity CRUD APIs as well as Custom Methods. It is also checked by the Standard Data Source to prevent sorting, searching, and filtering by properties that a user is not permitted to read.

Internal Properties

Properties can be hidden from Coalesce entirely, either with the [InternalUse] attribute or non-public C# access modifiers.

The properties in the following example are hidden entirely from all Coalesce functionality and generated APIs:

c#
using IntelliTect.Coalesce.DataAnnotations;
+public class Employee
+{
+  // InternalUseAttribute hides anything from Coalesce.
+  [InternalUse]
+  public string Name { get; set; }
+
+  // Non-public C# access modifiers will hide properties from Coalesce:
+  internal decimal Salary { get; set; }
+
+  // Property's type is [InternalUse], so properties using that type are also internal.
+  public Department Department { get; set; }
+}
+
+[InternalUse]
+public class Department
+{
+  // All properties on an [InternalUse] type are non-exposed,
+  // since the parent type is not exposed.
+  public string Name { get; set; }
+}

Read-Only Properties

A property in Coalesce can be made read-only in any of the following ways:

c#
using IntelliTect.Coalesce.DataAnnotations;
+using System.ComponentModel;
+public class Employee
+{
+  // A property with a [Read] attribute but no [Edit] attribute is read-only:
+  [Read]
+  public string Name { get; set; }
+
+  // Payroll users and HR users can read this property. Nobody can edit it:
+  [Read("Payroll,HR")]
+  public decimal Salary { get; set; }
+
+  // Using System.ComponentModel.ReadOnlyAttribute:
+  [ReadOnly(true)]
+  public DateTime BirthDate { get; set; }
+
+  // Non-public setter:
+  public DateTime StartDate { get; internal set; }
+
+  // No setter:
+  public string EmploymentDuration => (DateTime.Now - StartDate).ToString();
+
+  // Edits denied:
+  [Edit(SecurityPermissionLevels.DenyAll)]
+  public string EmployeeNumber { get; set; }
+}

Role Restrictions

Reading and writing a property in Coalesce can be restricted by roles:

c#
using IntelliTect.Coalesce.DataAnnotations;
+public class Employee
+{
+  // A property with no attributes is readable and writable without restriction
+  public string Name { get; set; }
+
+  // When a [Read] and [Edit] attributes are both present,
+  // the read roles are required for edits in addition to any edit roles.
+  // Property is only readable by Payroll & HR,
+  // and is also only editable by Payroll & HR.
+  [Read("Payroll,HR"), Edit]
+  public DateTime BirthDate { get; set; }
+
+  // Property is readable by Payroll and HR, and editable only by Payroll.
+  [Read("Payroll", "HR"), Edit("Payroll")]
+  public decimal Salary { get; set; }
+
+  // Property is readable by Payroll, and editable only by a user who is both Payroll AND HR.
+  [Read("Payroll"), Edit("HR")]
+  public DateTime StartDate { get; set; }
+
+  // Init-only properties on entities can only be set by the first /save of the entity.
+  public string EmployeeNumber { get; init; }
+}

A few of the examples above point out that when a property is restricted for reading by roles, those roles are also required when editing that property. This is because it usually doesn't make sense for a user to change a value when they have no way of knowing what the original value was. If you have a situation where a property should be editable without knowing the original value, use a custom method on the model to accept and set the new value.

Custom Restrictions

In addition to role-based property restrictions, you can also define property restrictions that can execute custom code for each model instance if your logic require more nuanced decisions than can be made with roles.

c#
using IntelliTect.Coalesce.DataAnnotations;
+public class Employee 
+{
+  public int Id { get; set; }
+
+  [Read]
+  public string UserId { get; set; }
+
+  [Restrict<SalaryRestriction>]
+  public decimal Salary { get; set; }
+}
+
+public class SalaryRestriction(MyUserService userService) : IPropertyRestriction<Employee>
+{
+  public bool UserCanRead(IMappingContext context, string propertyName, Employee model)
+    => context.User.GetUserId() == model.UserId || userService.IsPayroll(context.User);
+
+  public bool UserCanWrite(IMappingContext context, string propertyName, Employee model, object incomingValue)
+    => userService.IsPayroll(context.User);
+
+  public bool UserCanFilter(IMappingContext context, string propertyName)
+    => userService.IsPayroll(context.User);
+}

Restriction classes support dependency injection, so you can inject any supplemental services needed to make a determination.

The UserCanRead method controls whether values of the restricted property will be mapped from model instances to the generated DTO. Similarly, UserCanWrite controls whether the property can be mapped back to the model instance from the generated DTO.

The UserCanFilter method has a default implementation that returns false, but can be implemented if there is an appropriate, instance-agnostic way to determine if a user can sort, search, or filter values of that property.

Multiple different restrictions can be placed on a single property; all of them must succeed for the operation to be permitted. Restrictions also stack on top of role attribute restrictions ([Read] and [Edit]).

Row-level Security

Data Sources

In Coalesce, Data Sources are the mechanism that you can extend to implement row-level security on your Entities and Custom DTOs.

Data Sources are used when fetching results for /get, /list, and /count endpoints, and when fetching the target or result of a /save, /bulkSave, or /delete, and when fetching the invocation target of an Instance Method.

By default, your entities will be fetched using the Standard Data Source, but you can declare a custom default data source for each of your entities to override this default functionality. The default functionality here includes the default loading behavior, a feature where the Standard Data Source automatically includes the immediate relationships of requested entities. This can be suppressed by overriding the GetQuery method on your custom data source and not calling the base method, or by placing [Read(NoAutoInclude = true)] on classes or navigation properties that you do not want automatically included.

For most use cases, all your security rules will be implemented in the GetQuery/GetQueryAsync method. This is the most foundational method of the data source that all other functions in the data source build upon. Any predicates applied to the query of a type's default data source will affect all of the type's generated API endpoints (except for static custom methods).

There are a few different techniques that you can use to apply filtering in a data source, each one working for a specific use case. The example below includes an example of each technique.

Query Predicates

The Query Predicates technique involves applying a .Where() predicate to your query to filter the root entities that are returned by the query using some database-executed logic. This is a form of row-level security and can be used to only include a record based on the values of that record in the database.

Conditional Includes

The Conditional Includes technique involves conditionally appending .Include() calls to your query only when some server-executed criteria is met. Usually this involves checking the roles of a user and only including a navigation property if the user is in the requisite role. This technique cannot be used with database-executed logic and is therefore behaves more like table-level security than row-level security.

Filtered Includes

The Filtered Includes technique involves using EF Core filtered includes to apply database-executed logic to filter the rows of child collection navigation properties.

EF filtered Includes cannot be used to apply database-executed filters to reference navigation properties due to lack of EF support - see the sections below on transform results and global query filters for two possible solutions.

A complex example using all three of the above techniques:

c#
public class Employee
+{
+  public int EmployeeId { get; set; }
+  public bool IsIntern { get; set; }
+  public List<DepartmentMember> DepartmentMembers { get; set; }
+
+  // Override the default data source for Employee with a custom one:
+  [DefaultDataSource]
+  public class DefaultSource : StandardDataSource<Employee, AppDbContext>
+  {
+    public DefaultSource(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override IQueryable<Employee> GetQuery(IDataSourceParameters parameters) {
+      IQueryable<Employee> query = Db.Employees;
+
+      // TECHNIQUE: Conditional Includes - subset child objects using server-executed logic:
+      if (User.IsInRole("HR")) {
+        // HR can see everything. Return early so they are not subjected to the other filters:
+        return query.Include(e => e.DepartmentMembers).ThenInclude(dm => dm.Department);
+      }
+
+      // TECHNIQUE: Query Predicates - subset root objects using database-executed logic:
+      int employeeId = User.GetEmployeeId();
+      query = query.Where(e =>
+          // Anyone can see interns
+          e.IsIntern ||
+          // Otherwise, a user can only see employees in their own departments:
+          e.DepartmentMembers.Any(dm => dm.Department.DepartmentMembers.Any(u => u.EmployeeId == employeeId))
+        );
+
+      // TECHNIQUE: EF Core Filtered Includes - subset collections using database-executed logic.
+      // Include the departments of employees, but only those that the current user is a member of.
+      query = query.Include(e => e.DepartmentMembers
+        .Where(dm => dm.Department.DepartmentMembers.Any(u => u.EmployeeId == employeeId)))
+        .ThenInclude(dm => dm.Department);
+
+      return query;
+    }
+  }
+}
+
+public class Department
+{
+  public int DepartmentId { get; set; }
+  public string Name { get; set; }
+  public List<DepartmentMember> DepartmentMembers { get; set; }
+
+  // Override the default data source for Department with a custom one:
+  [DefaultDataSource]
+  public class DefaultSource : StandardDataSource<Department, AppDbContext>
+  {
+    public DefaultSource(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override IQueryable<Department> GetQuery(IDataSourceParameters parameters) {
+      IQueryable<Department> query = Db.Departments
+        .Include(e => e.DepartmentMembers).ThenInclude(dm => dm.Employee);
+
+      if (!User.IsInRole("HR"))
+      {
+        // Non-HR users can only see their own departments:
+        query = query.Where(d => d.DepartmentMembers.Any(dm => dm.EmployeeId == User.GetEmployeeId()));
+      }
+
+      return query;
+    }
+  }
+}
+
+// Only HR can directly read or modify DepartmentMember records.
+[Read("HR"), Create("HR"), Edit("HR"), Delete("HR")]
+public class DepartmentMember
+{
+  public int Id { get; set; }
+
+  public int DepartmentId { get; set; }
+  public Department Department { get; set; }
+  public int EmployeeId { get; set; }
+  public Employee Employee { get; set; }
+}

Transform Results

There exists a fourth technique in Data Sources for applying filtered includes: the TransformResultsAsync method. Unlike the other techniques above that are performed in the GetQuery method and applied at the beginning of the data source query pipeline, TransformResults is applied at the very end of the process against the materialized results. It also only affects the responses from the generated /get, /list, /save, /bulkSave, and /delete endpoints - it has no bearing on the invocation target of instance methods.

The primary purpose of TransformResults is to conditionally load navigation properties. This was very useful before EF Core introduced native filtered includes for collection navigation properties, and is still useful for applying filtered includes to reference navigation properties since EF does not support this. It can also be used for any kind of filtered includes if native EF filtered includes get translated into poorly-performant SQL, or it can be used to populate external type or other non-database-mapped properties on your entities.

The general technique for using TransformResults involves using EF Core Explicit Loading to attach additional navigation properties to the result set, and then using Coalesce's .IncludedSeparately() method in the data source's GetQuery so that Coalesce can still build the correct Include Tree to shape the serialization of your results.

c#
public class Employee
+{
+  public int EmployeeId { get; set; }
+  public int ManagerId { get; set; }
+  public Employee Manager { get; set; }
+
+  [DefaultDataSource]
+  public class DefaultSource : StandardDataSource<Employee, AppDbContext>
+  {
+    public DefaultSource(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override IQueryable<Employee> GetQuery(IDataSourceParameters parameters)
+      // Use IncludedSeparately to instruct Coalesce that we're going to
+      // manually populate the Manager, and that it should be mapped to the result DTOs
+      // despite not being eagerly loaded with EF's .Include() method.
+      => Db.Employees.IncludedSeparately(e => e.Manager);
+
+    public override async Task TransformResultsAsync(
+      IReadOnlyList<Employee> results,
+      IDataSourceParameters parameters
+    )
+    {
+      foreach (var employee in results)
+      {
+        // Only load the employee's manager if the current logged in user is that manager.
+        if (employee.ManagerId == User.GetEmployeeId() && employee.Manager is null) {
+          await Db.Employees.Where(e => e.EmployeeId == employee.ManagerId).LoadAsync();
+        }
+      }
+    }
+  }
+}

Alternatively, and indeed preferably, you can often formulate a query that does not use iteration and requires only a single database round-trip:

c#
public override async Task TransformResultsAsync(
+  IReadOnlyList<Employee> results,
+  IDataSourceParameters parameters
+)
+{
+  var managerIds = results.Select(e => e.ManagerId).ToList();
+  await Db.Employees
+    .Where(e => managerIds.Contains(e.ManagerId) && e.EmployeeId == User.GetEmployeeId())
+    .LoadAsync();
+}

Behaviors

In Coalesce, Behaviors are the extension point to implement row-level security or other customizations of create/edit/delete operations on your Entities and Custom DTOs. Behaviors are implemented on top of data sources, meaning the client request will be rejected if the requested entity for modification cannot be loaded from the entity's default data source.

By default, each entity will use the Standard Behaviors, but you can declare a custom behaviors class for each of your entities to override this default functionality.

For most use cases, all your security rules will be implemented in the BeforeSave/BeforeSaveAsync and BeforeDelete/BeforeDeleteAsync methods.

For a more complete explanation of everything you can do with behaviors, see the full Behaviors documentation page.

EF Global Query Filters

Since Coalesce's data access layer is built on top of Entity Framework, you can also use Entity Framework's Global Query Filters feature to apply row-level security.

This approach is less flexible than custom Coalesce data sources and has other drawbacks as well, but on the other hand it has more absolute authority, is less susceptible to issues like inadvertently returning data through unfiltered navigation properties, and can sometimes require less work to implement than individual data sources.

Global Query Filters are also the only way to implement database-executed filtered includes of reference navigation properties, as there is no version of .Include() for reference navigation properties that allows a database-executed predicate to be applied. See this open issue on EF Core.

Foreign Key Injection Vulnerabilities

When a user is saving a model with Coalesce, they can provide values for the model's foreign key properties. When this interaction takes place through a user interface, the user is not likely to produce a foreign key referencing an object that the user is not allowed to view.

A malicious user, however, is a different story. Imagine a user who is brute-forcing the /save endpoint on one of your entities, enumerating values of a foreign key. The may be trying to leak data through navigation property values returned by the response from the save, or they may be trying to inject their data into an object graph that they do not otherwise have access to.

If this scenario sounds like a plausible threat vector your application, be sure to perform sufficient validation of incoming foreign keys to ensure that the user is allowed to use a particular foreign key value before saving it to your database.

Also consider making any required foreign keys that should not change for the lifetime of an entity into init-only properties (i.e. use the init accessor in C# instead of the set accessor). While this does not entirely solve the foreign key injection issue, it eliminates the need to validate that a user is not changing the parent of an object if such an operation is not desirable.

Server-side Data Validation

Coalesce, as of version 4, will by default perform server-side validation of incoming data using validation attributes.

Your database will also enforce any constraints (referential integrity, not null, check constraints, etc.), but errors produced by your database will manifest as exceptions, which are not user-friendly.

For any custom validation that cannot be implemented by attributes, you must implement that yourself for saves and deletes or custom methods.

Attribute Validation

Historically, Coalesce did not provide any automatic, attribute-based validation of incoming data. As of Coalesce 4.0, automatic server side validation using ValidationAttribute-derived attributes on your models is enabled by default.

In addition to any validation attributes present on your model properties and method parameters, there are some other rules that work similarly to the default validation in ASP.NET Core:

  • The C# 11 required keyword also acts like a RequiredAttribute
  • If C# nullable reference types are enabled, non-nullable reference types are required required.
  • Non-nullable value types are implicitly optional, with the exception of non-nullable foreign keys, which are required.

To disable this functionality for your entire application, disable the corresponding configuration options on CoalesceOptions. For example, in Startup.cs or Program.cs:

c#
services.AddCoalesce<AppDbContext>(b => b.Configure(o =>
+{
+    // Set either to false to disable:
+    o.ValidateAttributesForSaves = true;
+    o.ValidateAttributesForMethods = true;
+}));

Each option also has a more granular override:

Enabling ValidateAttributesForSaves causes the Standard Behaviors to perform validation of validation attributes during /save or /bulkSave calls, preventing a save when validation fails. This can be overridden per type or even per request by setting the ValidateAttributesForSaves property on a custom Behaviors instance.

Enabling ValidateAttributesForMethods causes the generated controllers for custom methods to perform validation of incoming parameters. Validation attributes may be placed on method parameters, and validation will also be performed against the members of any complex type parameters. This can be overridden per method by setting the ValidateAttributes property on ExecuteAttribute for the method.

Saves and Deletes

Validation of /save, /bulkSave, and /delete actions against Entities and Custom DTOs are performed by the Behaviors for the type. Automatic attribute based validation can be used (saves only), or Behaviors can be overridden to perform validation and other customization of the save and delete process, as in the following example:

c#
public class Employee
+{
+  public int IsCeo { get; set; }
+  public decimal Salary { get; set; }
+
+  [Coalesce]
+  public class Behaviors : StandardBehaviors<Employee, AppDbContext>
+  {
+    public Behaviors(CrudContext<AppDbContext> context) : base(context) { }
+
+    public override ItemResult BeforeSave(SaveKind kind, Employee? oldItem, Employee item)
+    {
+      // `oldItem` is a shallow copy of entity from the database,
+      // and `item` is the tracked entity with incoming user data applied to it.
+      if (item.Salary > 1_000_000m && !oldItem.IsCeo) return "Salary is too high.";
+      return true;
+    }
+
+    public override ItemResult BeforeDelete(Case item)
+    {
+      if (item.IsCeo) return "The CEO cannot be fired.";
+      return true;
+    }
+  }
+}

Custom Methods and Services

For Custom Methods and Services, you can perform your own custom validation and return errors when validation fails. You can also use attribute based validation. Custom methods that need to return errors to the client are recommended to wrap their return type in an ItemResult<T>, allowing errors to be received and handled elegantly by your Coalesce Typescript code.

c#
public class Employee
+{
+  public decimal Salary { get; set; }
+
+  [Coalesce]
+  public ItemResult<decimal> GiveRaise(decimal raiseAmount)
+  {
+    if (raiseAmount > 3.5m) return "Raises must be less than $3.50."
+    Salary += raiseAmount;
+    return Salary;
+  }
+}

Security Overview Page

Coalesce provides batteries-included page that you can view to review the effective security rules in place for all the Coalesce-generated code in your project. Add this page to your application by mapping it as a route, either directly on WebHost in .NET 6+, or in UseEndpoints for 3.1+.

TIP

If you include the security overview in your production app, you should secure it with an authorization policy like in the example below. Alternatively, only map the endpoint in non-production environments.

c#
// .NET 6+ Program.cs:
+app.MapCoalesceSecurityOverview("coalesce-security").RequireAuthorization(
+    new AuthorizeAttribute { Roles = env.IsDevelopment() ? null : "Admin" }
+);
+
+// .NET Core 3.1+ Startup.cs:
+app.UseEndpoints(endpoints =>
+{
+    endpoints.MapCoalesceSecurityOverview("coalesce-security").RequireAuthorization(
+        new AuthorizeAttribute { Roles = env.IsDevelopment() ? null : "Admin" }
+    );
+});

Example of the contents of the security overview page:

Testing Your Security

If your application has complex security requirements and/or sensitive data that needs to be protected, you are encouraged to invest time into creating a set of automated tests to ensure that it is working how you expect.

The most comprehensive way to do this is to build a suite of integration tests using Microsoft's in-memory test server infrastructure. Follow Microsoft's documentation to set up a test project, and then write tests against your API endpoints. You will want to substitute your Entity Framework database provider with an in-memory Sqlite instance, and add a mock authentication handler to simulate authentication (we're mainly focused on testing authorization, not authentication).


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/topics/startup.html b/topics/startup.html new file mode 100644 index 000000000..2477224b7 --- /dev/null +++ b/topics/startup.html @@ -0,0 +1,53 @@ + + + + + + Application Configuration | Coalesce + + + + + + + + + + + + + + + +
Skip to content

Application Configuration

In order for Coalesce to work in your application, you must register the needed services in your Startup.cs or Program.cs. Doing so is simple:

c#
public void ConfigureServices(IServiceCollection services)
+{
+    services.AddCoalesce<AppDbContext>();
+    ...
+}

This registers all the basic services that Coalesce needs in order to work with your EF DbContext. However, there are many more options available. Here's a more complete invocation of AddCoalesce that takes advantage of many of the options available:

c#
public void ConfigureServices(IServiceCollection services)
+{
+    services.AddCoalesce(builder => builder
+        .AddContext<AppDbContext>()
+        .UseDefaultDataSource(typeof(MyDataSource<,>))
+        .UseDefaultBehaviors(typeof(MyBehaviors<,>))
+        .UseTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"))
+        .Configure(o =>
+        {
+            o.ValidateAttributesForMethods = true; // note: true is the default
+            o.ValidateAttributesForSaves = true; // note: true is the default
+            o.DetailedExceptionMessages = true;
+            o.ExceptionResponseFactory = ctx =>
+            {
+                if (ctx.Exception is FileNotFoundException)
+                {
+                    ctx.HttpContext.Response.StatusCode = 404; // Optional - set a specific response code.
+                    return new IntelliTect.Coalesce.Models.ApiResult(false, "File not found");
+                }
+                return null;
+            };
+        });
+    );
+}

Available builder methods include:

public Builder AddContext<TDbContext>()

Register services needed by Coalesce to use the specified context. This is done automatically when calling the services.AddCoalesce<AppDbContext>(); overload.

public Builder UseDefaultDataSource(Type dataSource)

Overrides the default data source used, replacing the Standard Data Source. See Data Sources for more details.

public Builder UseDefaultBehaviors(Type behaviors)

Overrides the default behaviors used, replacing the Standard Behaviors. See Behaviors for more details.

public Builder UseTimeZone(TimeZoneInfo timeZone)

Specify a static time zone that should be used when Coalesce is performing operations on dates/times that lack timezone information. For example, when a user inputs a search term that contains only a date, Coalesce needs to know what timezone's midnight to use when performing the search.

public Builder UseTimeZone<ITimeZoneResolver>()

Specify a service implementation to use to resolve the current timezone. This should be a scoped service, and will be automatically registered if it is not already. This allows retrieving timezone information on a per-request basis from HTTP headers, Cookies, or any other source.

public Builder Configure(Action<CoalesceOptions> setupAction)

Configure additional options for Coalesce runtime behavior. Current options include options for server-side validation, and options for exception handling. See individual members for details.


Coalesce is a free and open-source framework created by IntelliTect to fill our desire to create better apps, faster. IntelliTect is a high-end software architecture and development consulting firm based in Spokane, Washington.

If you're looking for help with your software project, whether it be a Coalesce application, other technologies, or even just an idea, reach out to us at info@intellitect.com — we'd love to start a conversation! Our clients range from Fortune 100 companies to local small businesses and non-profits.

+ + + + \ No newline at end of file diff --git a/ts-logo-128.svg b/ts-logo-128.svg new file mode 100644 index 000000000..b65a93a8d --- /dev/null +++ b/ts-logo-128.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ts-logo-512.svg b/ts-logo-512.svg new file mode 100644 index 000000000..a46d53d49 --- /dev/null +++ b/ts-logo-512.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vite-logo.svg b/vite-logo.svg new file mode 100644 index 000000000..de4aeddc1 --- /dev/null +++ b/vite-logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/vue-logo.svg b/vue-logo.svg new file mode 100644 index 000000000..d4d5f0bdf --- /dev/null +++ b/vue-logo.svg @@ -0,0 +1,8 @@ + + + + +

ZW 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�zBE;A6`RoYnYVbGchb9$^iIU2 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 literal 0 HcmV?d00001 diff --git a/assets/inter-roman-vietnamese.paY3CzEB.woff2 b/assets/inter-roman-vietnamese.paY3CzEB.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..5a9f9cb9ca0cd78b6ea2f3e5c9d2838dc8895598 GIT binary patch literal 8492 zcmV+{A=BP>Pew8T0RR9103j>@5dZ)H07h5<03gEv0|eaw00000000000000000000 z0000Qfg~G@2plRuNLE2of^Y_4KT}jeRDl`*f;ca13aJRB4KTl60X7081B4(8f)W4( zAO(aP2Ot}wL8BwA#{rDwTbk^j37MN0nnkm<&CYCgamAG{BQEv-@{h({JoW2WsiKJ{ zny6DpNHPoG)7pQYy0^RMEdaY!8%0bCP)`3Bl0b67#zmoxM+wMNKk&@$|J=(RT-bHH z&ZRfSMH4riXiQO2mtGhuDmm0S*_?%n8{4L$t(=MqgOqF)PiZY%M!9Mg`Sy%wXsdWi zi&vE7_{K|0QofPlA|)dwE)T@Ldw-UVjnSme!@vrsm!}gjPXlfXhXJSKzO~;D_{{Ft zj#v{*BuL~hh*>+q?$fMk0}-VxizyHmlss&T2(Vn%$U|B3849(nV*F&`?B==1_Ccqdm|ZONTW&&@StM@DVh$ga-iLzu~Y&GUY_3sURj%X_ncfPmoA3C17NwajDXbzt)aEFj@Hu#+DKbyD?gcrUjTvlky0JwkaT2p z8AY2Qu-I6`8`6=EbfhC4=}1R9)LA0?t`iZ766832kO=LeFLgp3u?_&16bTh}*?wY` z29u=(+z0CbsIRUF)!9(t07}m;w`r94SH>0B--x_ovEiXmZ)gc(sTKfsm$@cOl_kKB zN)2Gol=8c&Goqr1n?d6e5Jt%HJd<)iXHgQ)^Rhmn93`fF;Kl{9m&So$tpP#T0KFe7K@E#vfJ8{t0S5CS zV4r0~E^7FF25T$%Y^d(U#(j8H@?`KvynmwSL7-2301rqk_+P&ibaEe_Wl#Eizfb8? zhcppoL>neROy zplht+UGz3~5sWb+6fE|u>f6a8;ijibrcN!R z%OtvX+N^2PENMf_Qh3-^ZZBP0X~#^do&u@bUbbu<9d(f8Z;AjxmY~QZqKntoYS(Di zuT}zdatJqJdZomZdPuNFvMCpctqloDU1yo2=sWVGFV@xOZs z8Je~B$nL}1Z$Ztb6hwWg5$8fQapIDgi53Qph^mk*Hojm4My|x$^mrcA%T`Sw%dY^} znZTQ{7W1K8YB{;I0Fw~8eEfiYFyufL?gUqb%oUWvLO~8<`P~D}eM2AkS6`i^g#Dt( zs7erIq=p4jf=yCgC)NEzzVhZKFM99dw$7%-d+enbyZcUaUTOK=Qz`MmVcC*x((=No zf=eB6X&VM<(ju}X3Cu>1$rdt2H7O0#$CpeKG|Q8X6ar`_F6r@xQM)B!XfbODc&O_Q zYuK=*qg3fxHX8EAGMe{pF<4BgBpdBUCLh^A!qyp`s>or4AgGzw9IG?7!)~nCUHi02 zR=O_GM(k#zdYSbSujOV(`(?9{=^5o5Nh@6-on%Z#U0)fldj0x&5kM!L@R5=B~ zvSP~{r#D|*3UxTEyXk4ArojO;^ z83*hm!k#W19N#u`S!$4EzN@o}Usi<>v{*o09>D?P1H{Uf4v~WDJ}OFq@w;d=_t=G# zE%!2SG|7D_ELvRgPWq?7BKmsp!@JY%061j8VVu9pYxG_w!Z$mfzG$EKp_Abu98A?< z-y@*>`n#7Ib_*@bmHh>OtL6=E6MuZ}oM=+c6W?`V_sp3rAg1Q~`(FGHX1(FMh%HY* zhCKBpziR8y@4u_2$xBmx`7>MAV@;%O@4x?ttt-(3mNQHVe+Lh>hyC5hH&lIih2v0w z*?~XBIz+|)Sp1nvk2<4n$!**fOAv8ReOGv?z?*U6Lbes-*E z?bVuv&jtEM7j>-gm3rEC|L>!8GDPg$vGZ)rrjc&nw#^^tB0uT*UB!C~YB%Jckm?K# z`_7o4(kJjuJ^z+IefzB~<^MU~b82&=>E+^d5^#ur$A9IC1OM9mcQf{8zn(YT2XlOaA8EgPb28~R%>6Rd`Af}br*}^MPVWj1b~Sx9^pyGe;E`=V z9JHV6Onqm1abu5kTkGVS>x-v6`R=K|bP$0JN!Y+y21{<*s8DR)xTK3OuMSePASCt3QVw$5(AEq846U_kcCO$xzWTefN|@I83w!$aq#D8jekW+S2#lO31#a z0f;h%9lCR|c-}>Li>*KbBw)jieH((n|B54rsjLtfns z$=v;w4V;%RhF|P#vTS@TAEZcjUtDnZ7vb1}*c%rvg!cX|UcA1sy``aX+~dazV^<6H zwRaxBipRFREF3v-`HfG`{Vy-FcwJ*tlf0@m4MVJdZbr3}%PaOg+>rm|f4^*BzsjUs zfy3;>;i%=x+rCeJeA^OTT&HVkXNNQ@-HhZs`uqbP?xlA*L62Q_ibXqGp&NP=*eq^O9O`zjCtX$b1JNT10; zWdB}Vu*d*N#7M6P80jS}lcZ$TV~nbl{@N#{WsN7&?W>Yp; zaKmrtVi3`pRYGE`}^zOw$bWb%$ ziQnIwTGk;Z!)h6adRfb~kECc5r)t@f+G%gSE+g7?hafW)btanagbSWC8wvpy=MJ7_ ze*X6=v*Xv-bDs^N005u@I3ivu6K3IRcRqnoRX!i2C?b`tPRVR1@eDJ=Ap0$cjSY}N z&5Pm+P?Jx1c+#`6g*7&kICA0PL40khY-&E88iKVLV9peyYdMfz%0vi#dU_Q zMmKJXfgk6&EIhV=o1&={9FEpTqf@w8#e}IQWWVG%xp}#K8^3`Wl=$nnVy-$RGifWc z1|sd)BW@hHx{H`7l8B*Wz=KQA(uTNv#-nN&;1s2fiNXkOU|ayJ^VYLQ0MyPUVwFWQ zI4x?}P}87E3v&64-vTNos%fCG!H_rO#9f`PQI$(k=wx?5#L3zXM6~Q-1G=~b4qza0 zS6pY)b)|kPX_WXVt5>^>2Nyu&(P)VCcA*5|wc*3t0RV=fYf(8?rU`=Ck>7zLRJ2 z^OBG4DErHE<#>6wcuSysS8mjzT3TmSMO{;G)%%UhrdnB(Hbv7kUE9(2x98d`?cL!x zrJPDmHRmJF=bZoJT<6@z5uC&y!XLrc;m!Ed_#yly{2sTHdx+b^?dN{TJ+Sl(} z_r3V*{4M@a8hn@o2oM1RGXQ{gt%^Zzt(A!l3`XUQ$tFdCwzfYbhcxL7hmA%nji`}~ zdl}M?XaR!(1~3sMC=<}qSe}tHq)FkpoKadiH={rR7ZBhAfSKqRGt#gk2NSYK;0XXl z5LC{zw*X9K0P#sUfGAHC$Ym@yi-Z9xC{6%@o8Hy~svJC_DgcU}MW3rwnLA$LgwNvUxz3#2p?C`sQ&L@KfbuCpEVuVn)+ zhjp(fzo-4!{-8~`wf8zLOLi4T-q^x}{~Y!RldOBki-#E51D z;*5QQ98TCCQ2{$Uo1pePMKb*sCoXh;_T)L{mC%2)#{L4r`2e?10AWIs}%D7cM)J8`7>|HI{^-K~ z`~&!H3K+oF%eE4@@q&WUHr7jVs~o!Xm$uWAk~?8t!#gDsx?KU?rcI{f`L$dEW_&~la&8Am z@?1DTchW&s2!#$DCxBOj-R+$k(`GvWG<`}{qP6yBme=CKn9y4v0v#KsWYCFR#V!ye z1Sf3;Fb!4UYSuT2D?n$K1nAmpw*>LIEAO2N`+)>?E{Q4aRDUUN({FrChiA-lW#+*24fC;3RAro57J;ybWtb_Ui+n7tg40{LiL)*lc4T}1d;naJ(?vaQ{=>li%GA7sVy4m8M~!XD9q7PH{r;?nzlR$x#- z?_y{%R%AmC`U(f{^L`cd%*dzC+ih-}Z1yG3-LiP`(pHzfOU|^pW2R#N)25?*fT@bX#D>Bjl1R| zwHfh9A3UqLgI2Gvo_4rzaBN&MbJ_2seV5~}Q9>6-uvHla0{dp{-TB%r(XJ(Mk$YsY zYM<14e#X~hS3n>*B`03?@S%sWkn(g3(}%`X*o7o840lp5klv zx^}8*=nkC_vvSJkfz*EcK;<_&M6z|indvep%PAjL)Dn82dmse+eie>@n9v1#Wx%_rut1);n&qm>u9ID(=q6k#DK zcus&+PEBY`3h-j0zq4JXTJ2@m14PG|moJ;5X{Qsy`W#+I6i3t{8^Z2a9*B`pQk>~5gEur>|DYp{w5AMiy?&}b0 zGA5tI{`~9o%w3GkpS|@*zQGpk+i2ec?3*JFPm~++OVLP9v}V5ox~nmA{~B>v$!xc> z7;U@4;-z5bnutQn9Oe-;x5PwVJXv58m4U*}!xRu8fGsQVpN}Zkn;?(;j919PMqsX@ zQ8|DEY&p|fKM{7mlgr)ti#?_9YOan>oT!#V4@a`JLQp)x@A64!wo64J}ETfgGJqe5F)r{N;AKFDTj8uSL1Uy zwA&et&*{)^_%-WN0}$d8kB5ID;|qcdadL?UmSy77!OAXjiH=~=brMP zfL5=C$LD8;-R?-_77-kCGehZ@t5S4biNGhmHH&_UKn?YZCRe&ehX{Y8iG++2)UJ3t zj4oG;PFw(s{&fZ3jr+)r^A|@kR3xF9jtB?Ea>nSVi@9$YT-}18go5d?&TY|UPFmy) zWvZirs~H=wefj+$e)sy<9_Jtpsi0}65+af!bCCz({78enTM*tv1zaM;nP?OEEpl+- z>ZndzVdFWAqjJ{v@PlHz^i7&B-7P6m4U{6h#2jPcS<%GCL)|jbq3k&t`Z{XFI%uZ0 zpJE{pivnmm0gh<+vXw=qQsjSK*rhieL@MnPhjw!z zj!S8#J$rk*w-irciA2onjU}uKNRknT@cGryFEtWI!e$ytS9c>Z zpI=d!2BNT#oJ4D)!#`qNPWHFN$SKJ53AMEk2EFz_9}WcHa=ULyYZpJJJ)zz@LAdo+ zUr*&txE|>QmJ1<|8_H`6J#I8!jV}pM&Mb-${H3d>06#%ysF^yVTF9zD$Qz$ zw=+g0;v^-=5sBt0%cUdWtU*|yf=}yv65wlsd_|bkc<)B zKx9dc^{DrQN=qkaJ$sJP#%VtP<-@b^)7JV?OJzGE&)s8$Bb^(xjKwgD_8^5}>Fn!} z7RqEoE0|7#FeBKPXdWC13#Tm5wC*cb%E60cd;)cX1-m-LLa$nY-G#9OZg;>sZX2^( zEI-`t^qVd+wKJ)7@^edzn)ea0S{lq{WcA=Le!u^hP97G<4Rfe)J*~7Grt@_~rqoZ@ zFDfdI9(|!&*6MLLxMKG9d$0C5kG^rDZwa%)1U`MaD@Hn(v`qFxkN0u(_I-?+F3$X! z<2^8@z*-GWUB#R{Kb{dYXG^B#xUJhUW|723Kw{x%205rX^N%&pEE@qJ| zpUvmT<3C4!42KDVoBlhS^%b{btyAr%r|;`xn8icS%{6PzocUMJQfA+G+UBfY4|?xNGi80s-MeEJcw)>?`o?XX8z;_Px+~5Jl6CB7FFRLhgO9VhW~F=A3uA!U}Jnj^|FB z>73KT!6*vD^cZdd+=}Bs5=mS@YG`mswX4SEkzOxJ21u9J>mtPIDEi=4Q*W-5QBK*M zhcR34QZI@x?*TovT2QG#K!dpn1X4;Uq^v6F#YqPa2I$6)lJ2M_=efg+$z-X&(_)c8 zAVQ5RaPg9r(~r6DnFHQzg>GC^FvJ`Ei!Vn{oOtc%GRELm*La#M>IXR0#7Hw_A5URe z6^2nbe)~?5P~L11PMIQEFuBCaE*8h6CW(p$^CA(RkK;Hc6xArfT=4zOO}OuUvR;yO zkfb-A9y2)YHby&D^K;NG^@x-gyt{Kx3oWLF&6h^uogboCQdS!5 z(JnC1;4ON!tmDa*#Q^&8a1J*wahPt7GeVeNUM{WI)NR=#2RpXdc{tEQy(E^XY(5rr zqjsR*6?}hS=*(cP$2*kzZy<f5&+Pk01)`@QF`0@L`kO=K*aX5dtz~l49AG-2TI$o zIxGnfH7A)0N7Xc%AdAJYbvjLhGaUmnTYGhF*-j9V;ewesc7tm>K#m60!Azw%uHJ*g z@-W&oRM>+PqmKb{ktFTvB5G%I>?fVt!cl3zn3WTLJrRt=`%>6f0lF>48E0!}NY+r^ z3h-t%6D}#Dz<-Uve^lD^xfK=|SE5yxqD@I-86OBkf*ge~GY)rnmCIY@5-do}1>h3U zhY>urVN`*~!z?qkmt*E|p1F>fv32J{3V{$3VG>+4JeKj|;UakMS>wiuAY2R)VBrTS z3drzy@Srq2f#RUzxlr(eMO53tXpdc5G&ATR@NW2Lk@N}F?%>wG<+h)wTkRT4W0xIT zZMDU^Q0^%m40cfdq}AXWTQz^#?KbIUb?087&Qc3T=&Q6KLz~P|x#hXGlGv=yAnI$e z!+x{`W}8W((tHawYtdnb1FkoNFuZQsV!I_e)m=7CQyVV(H~HA@Nxcm|Wt+7lZo9OA a!Vc~9U7SGB8$q3Ii#9CnAx#qS0RRAsKuhfa literal 0 HcmV?d00001 diff --git a/assets/introduction.md.aI2TP5X5.js b/assets/introduction.md.aI2TP5X5.js new file mode 100644 index 000000000..331e173da --- /dev/null +++ b/assets/introduction.md.aI2TP5X5.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as o,R as a,a3 as i,a4 as r,a5 as n,a6 as s,a7 as l,a8 as d}from"./chunks/framework.g9eZ-ZSs.js";const v=JSON.parse('{"title":"Introduction","description":"Documentation home page for IntelliTect.Coalesce","frontmatter":{"lang":"en-US","title":"Introduction","description":"Documentation home page for IntelliTect.Coalesce"},"headers":[],"relativePath":"introduction.md","filePath":"introduction.md"}'),c={name:"introduction.md"},p=a('