-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1776 from riganti/modal-dialog
New dot:ModalDialog control, wrapper for <dialog>
- Loading branch information
Showing
10 changed files
with
422 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
using System; | ||
using System.Net; | ||
using System.Text; | ||
using DotVVM.Framework.Binding; | ||
using DotVVM.Framework.Binding.Expressions; | ||
using DotVVM.Framework.Hosting; | ||
using DotVVM.Framework.ResourceManagement; | ||
using Newtonsoft.Json; | ||
|
||
namespace DotVVM.Framework.Controls | ||
{ | ||
/// <summary> | ||
/// Renders a HTML native dialog element, it is opened using the showModal function when the <see cref="Open" /> property is set to true | ||
/// </summary> | ||
/// <remarks> | ||
/// * Non-modal dialogs may be simply binding the attribute of the HTML dialog element | ||
/// * The dialog may be closed by button with formmethod="dialog", when ESC is pressed, or when the backdrop is clicked if <see cref="CloseOnBackdropClick" /> = true | ||
/// </remarks> | ||
[ControlMarkupOptions()] | ||
public class ModalDialog : HtmlGenericControl | ||
{ | ||
public ModalDialog() | ||
: base("dialog", false) | ||
{ | ||
} | ||
|
||
/// <summary> A value indicating whether the dialog is open. The value can either be a boolean or an object (not false or not null -> shown). On close, the value is written back into the Open binding. </summary> | ||
[MarkupOptions(AllowHardCodedValue = false)] | ||
public object? Open | ||
{ | ||
get { return GetValue(OpenProperty); } | ||
set { SetValue(OpenProperty, value); } | ||
} | ||
public static readonly DotvvmProperty OpenProperty = | ||
DotvvmProperty.Register<object, ModalDialog>(nameof(Open), null); | ||
|
||
/// <summary> Add an event handler which closes the dialog when the backdrop is clicked. </summary> | ||
public bool CloseOnBackdropClick | ||
{ | ||
get { return (bool?)GetValue(CloseOnBackdropClickProperty) ?? false; } | ||
set { SetValue(CloseOnBackdropClickProperty, value); } | ||
} | ||
public static readonly DotvvmProperty CloseOnBackdropClickProperty = | ||
DotvvmProperty.Register<bool, ModalDialog>(nameof(CloseOnBackdropClick), false); | ||
|
||
/// <summary> Triggered when the dialog is closed. Called only if it was closed by user input, not by <see cref="Open"/> change. </summary> | ||
public Command? Close | ||
{ | ||
get { return (Command?)GetValue(CloseProperty); } | ||
set { SetValue(CloseProperty, value); } | ||
} | ||
public static readonly DotvvmProperty CloseProperty = | ||
DotvvmProperty.Register<Command, ModalDialog>(nameof(Close)); | ||
|
||
protected override void AddAttributesToRender(IHtmlWriter writer, IDotvvmRequestContext context) | ||
{ | ||
var valueBinding = GetValueBinding(OpenProperty); | ||
if (valueBinding is {}) | ||
{ | ||
writer.AddKnockoutDataBind("dotvvm-modal-open", this, valueBinding); | ||
} | ||
else if (!(Open is false or null)) | ||
{ | ||
// we have to use the binding handler instead of `open` attribute, because we need to call the showModal function | ||
writer.AddKnockoutDataBind("dotvvm-modal-open", "true"); | ||
} | ||
|
||
if (GetValueOrBinding<bool>(CloseOnBackdropClickProperty) is {} x && !x.ValueEquals(false)) | ||
{ | ||
writer.AddKnockoutDataBind("dotvvm-modal-backdrop-close", x.GetJsExpression(this)); | ||
} | ||
|
||
if (GetCommandBinding(CloseProperty) is {} close) | ||
{ | ||
var postbackScript = KnockoutHelper.GenerateClientPostBackScript(nameof(Close), close, this, returnValue: null); | ||
writer.AddAttribute("onclose", "if (event.target.returnValue!=\"_dotvvm_modal_supress_onclose\") {" + postbackScript + "}"); | ||
} | ||
|
||
base.AddAttributesToRender(writer, context); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
src/Framework/Framework/Resources/Scripts/binding-handlers/modal-dialog.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
export default { | ||
"dotvvm-modal-open": { | ||
init(element: HTMLDialogElement, valueAccessor: () => any) { | ||
element.addEventListener("close", () => { | ||
const value = valueAccessor(); | ||
if (ko.isWriteableObservable(value)) { | ||
// if the value is object, set it to null | ||
value(typeof value.peek() == "boolean" ? false : null) | ||
} | ||
}) | ||
}, | ||
update(element: HTMLDialogElement, valueAccessor: () => any) { | ||
const value = ko.unwrap(valueAccessor()), | ||
shouldOpen = value != null && value !== false; | ||
if (shouldOpen != element.open) { | ||
if (shouldOpen) { | ||
element.returnValue = "" // reset returnValue, ESC key leaves the old return value | ||
element.showModal() | ||
} else { | ||
element.close("_dotvvm_modal_supress_onclose") | ||
} | ||
} | ||
}, | ||
}, | ||
"dotvvm-modal-backdrop-close": { | ||
init(element: HTMLDialogElement, valueAccessor: () => any) { | ||
// closes the dialog when the backdrop is clicked | ||
element.addEventListener("click", (e) => { | ||
if (e.target == element) { | ||
const elementRect = element.getBoundingClientRect(), | ||
x = e.clientX, | ||
y = e.clientY; | ||
if (x < elementRect.left || x > elementRect.right || y < elementRect.top || y > elementRect.bottom) { | ||
if (ko.unwrap(valueAccessor())) { | ||
element.close(); | ||
} | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
src/Samples/Common/ViewModels/FeatureSamples/ModalDialog/ModalDialogViewModel.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using System.Net.Http.Headers; | ||
using System.Text; | ||
using DotVVM.Framework.ViewModel; | ||
|
||
namespace DotVVM.Samples.Common.ViewModels.FeatureSamples.ModalDialog | ||
{ | ||
public class ModalDialogViewModel : DotvvmViewModelBase | ||
{ | ||
public bool Dialog1Shown { get; set; } | ||
public bool DialogChained1Shown { get; set; } | ||
public bool DialogChained2Shown { get; set; } | ||
public bool CloseEventDialogShown { get; set; } | ||
|
||
public int? NullableIntController { get; set; } | ||
public string NullableStringController { get; set; } | ||
|
||
public DialogModel DialogWithModel { get; set; } = null; | ||
|
||
public int CloseEventCounter { get; set; } = 0; | ||
|
||
public void ShowDialogWithModel() | ||
{ | ||
DialogWithModel = new DialogModel() { Property = "Hello" }; | ||
} | ||
|
||
public void CloseDialogWithEvent() | ||
{ | ||
CloseEventDialogShown = false; | ||
} | ||
|
||
public class DialogModel | ||
{ | ||
public string Property { get; set; } | ||
} | ||
} | ||
|
||
} |
79 changes: 79 additions & 0 deletions
79
src/Samples/Common/Views/FeatureSamples/ModalDialog/ModalDialog.dothtml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
@viewModel DotVVM.Samples.Common.ViewModels.FeatureSamples.ModalDialog.ModalDialogViewModel | ||
|
||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<title></title> | ||
|
||
<style> | ||
.button-active { | ||
background-color: #4CAF50; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<h1>Modal dialogs</h1> | ||
|
||
<p> | ||
<dot:Button data-ui="btn-open-simple" Text="Simple dialog" Click="{staticCommand: Dialog1Shown = true}" Class-button-active={value: Dialog1Shown} /> | ||
<dot:Button data-ui="btn-open-chained1" Text="Chained dialog" Click="{staticCommand: DialogChained1Shown = true}" Class-button-active={value: DialogChained1Shown} /> | ||
<dot:Button data-ui="btn-open-close-event" Text="Dialog with clickable backdrop and close event" Click="{staticCommand: CloseEventDialogShown = true}" Class-button-active={value: CloseEventDialogShown} /> | ||
<dot:Button data-ui="btn-open-view-model" Text="Dialog with view model" Click="{command: ShowDialogWithModel()}" Class-button-active={value: DialogWithModel != null} /> | ||
<dot:Button data-ui="btn-open-int" Text="Dialog controlled by nullable number" Click="{command: NullableIntController = 0}" Class-button-active={value: NullableIntController != null} /> | ||
<dot:Button data-ui="btn-open-string" Text="Dialog controlled by nullable string" Click="{staticCommand: NullableStringController = ""}" Class-button-active={value: NullableStringController != null} /> | ||
</p> | ||
<p> | ||
Close events: <span data-ui="close-event-counter" InnerText={value: CloseEventCounter} /> | ||
</p> | ||
|
||
<dot:ModalDialog Open={value: Dialog1Shown} data-ui=simple> | ||
<form> | ||
This is a simple modal dialog, close it by pressing ESC or clicking the <button data-ui=btn-close formmethod="dialog" type="submit">Form method=dialog</button> button. | ||
</form> | ||
</dot:ModalDialog> | ||
|
||
<dot:ModalDialog Open={value: DialogChained1Shown} data-ui=chained1> | ||
<p>This is the first chained modal dialog.</p> | ||
<form> | ||
<dot:Button data-ui=btn-next Text="Next" Click={staticCommand: DialogChained1Shown = false; DialogChained2Shown = true} /> | ||
<button data-ui=btn-close formmethod="dialog" type="submit">Cancel</button> | ||
</form> | ||
</dot:ModalDialog> | ||
|
||
<dot:ModalDialog Open={value: DialogChained2Shown} data-ui=chained2> | ||
<p>This is the second chained modal dialog.</p> | ||
<dot:Button data-ui=btn-close Text="Close" Click={staticCommand: DialogChained2Shown = false} /> | ||
</dot:ModalDialog> | ||
|
||
<dot:ModalDialog Open={value: CloseEventDialogShown} CloseOnBackdropClick Close={staticCommand: CloseEventCounter = CloseEventCounter + 1} data-ui=close-event> | ||
Closing the dialog will increase the counter. Either | ||
<ul> | ||
<li>Click the backdrop</li> | ||
<li>Press ESC</li> | ||
<li><dot:Button data-ui=btn-close-staticcommand Click={staticCommand: CloseEventDialogShown=false}>Use staticCommand</dot:Button></li> | ||
<li><dot:Button data-ui=btn-close-command Click={command: CloseDialogWithEvent()}>Use command</dot:Button></li> | ||
<li> <form method="dialog"><button data-ui=btn-close-form type="submit">Form method=dialog</button></form></li> | ||
</ul> | ||
</dot:ModalDialog> | ||
|
||
<dot:ModalDialog Open={value: DialogWithModel} data-ui=view-model> | ||
<p>Edit this field: <dot:TextBox Text={value: DialogWithModel.Property} /> </p> | ||
<p> | ||
<dot:Button data-ui=btn-save Text="Save" Click={command: DialogWithModel = null} /> | ||
<form method="dialog"><button data-ui=btn-close type="submit">Cancel</button></form> | ||
</p> | ||
</dot:ModalDialog> | ||
|
||
<dot:ModalDialog Open={value: NullableIntController} data-ui=int> | ||
the number: <dot:TextBox data-ui=editor Text={value: NullableIntController} /> | ||
<form method="dialog"><button data-ui=btn-close type="submit">Close</button></form> | ||
</dot:ModalDialog> | ||
|
||
<dot:ModalDialog Open={value: NullableStringController} data-ui=string> | ||
the string: <dot:TextBox data-ui=editor Text={value: NullableStringController} /> | ||
<form method="dialog"><button data-ui=btn-close type="submit">Close</button></form> | ||
</dot:ModalDialog> | ||
</body> | ||
</html> | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.