Closed event v Bootstrap 5 ModalDialog

Ahoj, mám problém s Bootstrap 5 ModalDialog.
Potřeboval bych tam nějaký event Closed při zavření dialogu, abych na to mohl navázat staticCommand. Jinak nemůžu použít CloseOnBackdropClick, CloseOnEscape a výchozí AddCloseButton.
Nyní se při zavření dialogu jen sama nastaví hodnota vlastnosti IsDisplayed na false, to ale funguje pouze při použití s bool property. Pokud bych použil binding např. IsDisplayed=“{value: ZboziInfo != null}”, tak na close potřebuji dělat ZboziInfo = null apod.

V mém případě jsem si zatím close button udělal vlastní, ale ten backdrop click a ESC jsem musel vypnout. A při zavření potřebuji volat ZboziInfo = null i když použiji bool vlastnost na IsDisplayed. Kód vypadá takto:

<bs:ModalDialog DataContext="{value: ZboziInfoViewModel}" IsDisplayed="{value: IsZboziInfoDisplayed}" UseBackdrop="true" AddCloseButton="false" CloseOnBackdropClick="false" CloseOnEscape="false" Size="XLarge">
    <HeaderTemplate>
        <span>Detail zboží</span>
        <dot:button class="btn-close" Click="{staticCommand: IsZboziInfoDisplayed = false; ZboziInfo = null}" />
    </HeaderTemplate>
    <Content>
        ...
    </Content>
    <FooterTemplate>
        <bs:Button Text="Close" Click="{staticCommand: IsZboziInfoDisplayed = false; ZboziInfo = null}" />
    </FooterTemplate>
</bs:ModalDialog>

Potřeboval bych něco jako:

<bs:ModalDialog DataContext="{value: ZboziInfoViewModel}" IsDisplayed="{value: ZboziInfo != null}" UseBackdrop="true" AddCloseButton="true" CloseOnBackdropClick="true" CloseOnEscape="true" Size="XLarge" Closed="{staticCommand: ZboziInfo = null}">
    <HeaderTemplate>
        <span>Detail zboží</span>
    </HeaderTemplate>
    <Content>
        ...
    </Content>
    <FooterTemplate>
        <bs:Button Text="Close" Click="{staticCommand: ZboziInfo = null}" />
    </FooterTemplate>
</bs:ModalDialog>

Díky

Pokusil jsem se o to sám. Je to funkčí, ale asi to bude chtít učesat.

Nevím jak přesně udělat Command z js cutom eventu. Teď to nechám nastavit do attributu a pak to v js místo dispatchEvent volám přes apply. Neexistuje lepší řešení?
Ideálně to pak přidat do verze.

Udělali jsem si svojí kopii controlu ModalDialog:

public class BootstrapModalDialog : BootstrapCompositeControl
{
    public static readonly DotvvmProperty ClosedProperty = DotvvmProperty.Register<Command, BootstrapModalDialog>((BootstrapModalDialog t) => t.Closed);

    /// <summary>
    /// Gets or sets the command that will be triggered when the modal is closed.
    /// </summary>
    public Command? Closed
    {
        get { return (Command?)GetValue(ClosedProperty); }
        set { SetValue(ClosedProperty, value); }
    }

    public static DotvvmControl GetContents(....

    protected override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context)
    {
        var closedBinding = GetCommandBinding(ClosedProperty);

        base.AddAttributesToRender(writer, context);

        if (closedBinding != null)
        {
            writer.AddAttribute("data-on-closed-bootstrapmodaldialog", KnockoutHelper.GenerateClientPostBackScript(nameof(Closed),
                closedBinding, this, useWindowSetTimeout: true, isOnChange: false), true, ";");
        }
    }
}

metoda GetContents je stejná

A pak v kopii dotvvm-bootstrap.js mám změněný init:

    init: (element, valueAccessor, allBindings, viewModel, bindingContext) => {
        var modal = new bootstrap.Modal(element);

        if (modal) {
            element.addEventListener('hidden.bs.modal', () => {
                const prop = valueAccessor();
                if (ko.isWriteableObservable(prop)) {
                    prop(false);
                }

                //Call function in data-on-closed-bootstrapmodaldialog attribute
                var onClosedJsCode = element.getAttribute('data-on-closed-bootstrapmodaldialog');
                new Function(onClosedJsCode).apply(element, [new Event("closed.bootstrapmodaldialog")]);
            });
            element.addEventListener('shown.bs.modal', () => {
                const prop = valueAccessor();
                if (ko.isWriteableObservable(prop)) {
                    prop(true);
                }
            });
            const closeButton = element.querySelector(".btn-close");
            if (closeButton) {
                closeButton.addEventListener("click", () => {
                    const prop = valueAccessor();
                    if (ko.isWriteableObservable(prop)) {
                        prop(false);
                    }

                    //Call function in data-on-closed-bootstrapmodaldialog attribute
                    var onClosedJsCode = element.getAttribute('data-on-closed-bootstrapmodaldialog');
                    new Function(onClosedJsCode).apply(element, [new Event("closed.bootstrapmodaldialog")]);
                });
            }
        }
    },

Tam je to volání js codu, který je v attributu data-on-closed-bootstrapmodaldialog, což je vygenerované volání postbacku co potřebuji.

Použití je:

<local:ModalDialog DataContext="{value: ZboziInfoViewModel}" Closed="{staticCommand: ZboziInfo = null}" IsDisplayed="{value: ZboziInfo != null}" UseBackdrop="true" AddCloseButton="false" CloseOnBackdropClick="true" CloseOnEscape="true" Size="XLarge" VerticalAlignment="Center">
    <Content>
        <div class="modal-close-button">
            <dot:LinkButton class="close-button" Click="{staticCommand: ZboziInfo = null}">{{resource: Resources.Global_Close}}</dot:LinkButton>
        </div>
        <local:ZboziInfo DataContext="{value: ZboziInfo}" />
    </Content>
</local:ModalDialog>

Pardon, že na to neodpovídám, já se v BS5 vůbec neorientuju.

Každopádně předat funkci do knockout binding handleru by v pohodě mělo jít i bez new Function: Místo volání writer.AddAttribute bych funkci předal do writer.AddKnockoutDataBind a pak si jí vytáhnul z allBindings. Ten skript akorát je akorát potřeba vygenerovat pomocí KnockoutHelper.GenerateClientPostbackLambda. Nejsem si teď jistý, ale pravděpodobně bude knockout.js potřebovat mít zaregistrovaný prázdný knockout handler data-on-closed-bootstrapmodaldialog.

Každopádně mi přijde, že bychom tohle měli přidat, ostatní modaly to předpokládám umí… Zkusím to tam dodělat.

Takže ta C# část by byla takto:

protected override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context)
{
    var closedBinding = GetCommandBinding(ClosedProperty);

    base.AddAttributesToRender(writer, context);

    if (closedBinding != null)
    {
        writer.AddKnockoutDataBind("closed", KnockoutHelper.GenerateClientPostbackLambda(nameof(this.Closed), closedBinding, this));
    }
}

Ale tu JS část dohromady nedám.

Pokud by to šlo přidat to DotVVM.Controls.Bootstrap5 tak budu velice rád.
Díky moc.

JS část by podle mě měla být jenom allBindings.closed(), funguje to?

    init: (element, valueAccessor, allBindings, viewModel, bindingContext) => {
        var modal = new bootstrap.Modal(element);

        if (modal) {
            element.addEventListener('hidden.bs.modal', () => {
                const prop = valueAccessor();
                if (ko.isWriteableObservable(prop)) {
                    prop(false);
                }

                allBindings.closed()
            });
            element.addEventListener('shown.bs.modal', () => {
                const prop = valueAccessor();
                if (ko.isWriteableObservable(prop)) {
                    prop(true);
                }
            });
            const closeButton = element.querySelector(".btn-close");
            if (closeButton) {
                closeButton.addEventListener("click", () => {
                    const prop = valueAccessor();
                    if (ko.isWriteableObservable(prop)) {
                        prop(false);
                    }

                    allBindings.closed()
                });
            }
        }
    },

Píše to allBindings.closed is not a function

Vyrenderovaný je to takto:

Ještě takhle vypadají ty allBindings:

Aha pardon, mělo by to být allBindings.get("closed")()

Funguje.
Super, díky moc.

1 Like