-
-
Notifications
You must be signed in to change notification settings - Fork 3
114 javascript signals for server #116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f360785
2ca5ec9
2748df1
f595196
e7e2fa7
fb8f516
3509cb8
e8337f4
02427ab
56b242d
d5d0543
8acd01a
75d063a
4fd1925
12fdfa4
5135ce0
23cf4e3
7ce06c7
e25e0fc
a13704e
380fc59
728d478
c96d89f
3708f09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
|
|
||
| <h3>Hello @Data.Name</h3> | ||
|
|
||
| @code { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
|
|
||
| <p id="HelloWorld"></p> | ||
| <script> | ||
|
|
||
| var element = document.getElementById("HelloWorld"); | ||
|
|
||
|
|
||
| if (element) { | ||
| element.innerHTML = "<h1>Hello from JavaScript</h1>"; | ||
| } | ||
|
|
||
| // Call the globally defined blazorReport.completed() to let BlazorReports know the report's contents have finished generating. | ||
| setTimeout(() => { | ||
| blazorReport.completed(); | ||
| }, @Data.TimeSpan.TotalMilliseconds) | ||
|
|
||
| </script> | ||
| @code { | ||
| [Parameter] | ||
| public required SimpleJsAsyncReportData Data { get; set; } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| namespace SimpleReportServer.Reports; | ||
|
|
||
| public record SimpleJsAsyncReportData(int TimeoutInSeconds) | ||
| { | ||
| public TimeSpan TimeSpan => TimeSpan.FromSeconds(TimeoutInSeconds); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,90 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <style>@((MarkupString)BaseStyles)</style> | ||
| <style>@((MarkupString)BaseStyles)</style> | ||
| <script> | ||
| // Define BlazorReport class | ||
| class BlazorReport { | ||
| constructor(signalName) { | ||
| this.signalName = signalName; | ||
| } | ||
|
|
||
| notReady() { | ||
| window[this.signalName] = "not-ready"; | ||
| } | ||
|
|
||
| completed() { | ||
| window[this.signalName] = "ready"; | ||
| } | ||
| } | ||
|
|
||
| // Create global instance with the provided signal name | ||
| window.blazorReport = new BlazorReport("@ReportIsReadySignal"); | ||
|
|
||
| // Function to wait for signal change | ||
| window.waitForSignal = (timeoutMs = 5000, intervalMs = 50) => { | ||
| var expectedValue = 'ready' | ||
| return new Promise((resolve, reject) => { | ||
| const start = Date.now(); | ||
|
|
||
| const check = () => { | ||
| if (window["@ReportIsReadySignal"] === expectedValue) { | ||
| clearInterval(interval); | ||
| resolve('Signal received'); | ||
| } else if (Date.now() - start > timeoutMs) { | ||
| clearInterval(interval); | ||
| reject(new Error('Timeout waiting for signal')); | ||
| } | ||
| }; | ||
|
|
||
| const interval = setInterval(check, intervalMs); | ||
| check(); // In case it's already ready | ||
| }); | ||
| }; | ||
| </script> | ||
| </head> | ||
| <body> | ||
| <DynamicComponent Type="@ChildComponentType" Parameters="@ChildComponentParameters" /> | ||
| <DynamicComponent Type="@ChildComponentType" Parameters="@ChildComponentParameters" /> | ||
|
|
||
| @foreach (var script in Scripts) | ||
| { | ||
| <script> | ||
| @((MarkupString)@script.Value) | ||
| </script> | ||
| } | ||
| </body> | ||
| </html> | ||
|
|
||
|
|
||
| @code { | ||
| /// <summary> | ||
| /// The base styles to be applied to the component. | ||
| /// </summary> | ||
| [Parameter] | ||
| public string BaseStyles { get; set; } = ""; | ||
| /// <summary> | ||
| /// The type of the component to be rendered in the html body. | ||
| /// </summary> | ||
| [Parameter] | ||
| public Type ChildComponentType { get; set; } = null!; | ||
| /// <summary> | ||
| /// The parameters to be passed to the child component. | ||
| /// </summary> | ||
| [Parameter] | ||
| public IDictionary<string, object> ChildComponentParameters { get; set; } = null!; | ||
| /// <summary> | ||
| /// The base styles to be applied to the component. | ||
| /// </summary> | ||
| [Parameter] | ||
| public string BaseStyles { get; set; } = ""; | ||
|
|
||
| /// <summary> | ||
| /// The type of the component to be rendered in the html body. | ||
| /// </summary> | ||
| [Parameter] | ||
| public Type ChildComponentType { get; set; } = null!; | ||
|
|
||
| /// <summary> | ||
| /// The parameters to be passed to the child component. | ||
| /// </summary> | ||
| [Parameter] | ||
| public IDictionary<string, object> ChildComponentParameters { get; set; } = null!; | ||
|
|
||
| /// <summary> | ||
| /// The scripts to be included in the html body. | ||
| /// </summary> | ||
| [Parameter] | ||
| public Dictionary<string, string> Scripts { get; set; } = new Dictionary<string, string>(); | ||
|
|
||
| /// <summary> | ||
| /// The name of the JavaScript signal used to indicate when the report is ready. | ||
| /// </summary> | ||
| [Parameter] | ||
| public string ReportIsReadySignal { get; set; } = "reportIsReady"; | ||
|
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,4 +44,9 @@ public class BlazorReport | |
| /// The page settings to use for the report. | ||
| /// </summary> | ||
| public BlazorReportsPageSettings? PageSettings { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The current report javascript settings. | ||
| /// </summary> | ||
| public required BlazorReportCurrentReportJavascriptSettings CurrentReportJavascriptSettings { get; set; } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To mantain consistency use BlazorReportsJavascriptSettings |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| namespace BlazorReports.Models; | ||
|
|
||
| /// <summary> | ||
| /// Settings for the internal javascript api | ||
| /// </summary> | ||
| public class BlazorReportCurrentReportJavascriptSettings | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now one single configuration obect structure might suffice? Basically joining the global and current report into on single BlazorReportJavascriptSettings
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, makes sense
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question, if the person does WaitForJavascriptCompletedSignal what should be the default timeout value?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably let's leave it at 1s if it's not completed, to not affect the performance too much if they forget. But this will throw an error right? |
||
| { | ||
| /// <summary> | ||
| /// The signal that the report is ready | ||
| /// </summary> | ||
| public string ReportIsReadySignal { get; set; } = default!; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will be internal for the time being.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should I remove it from both the settings?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes |
||
|
|
||
| /// <summary> | ||
| /// Decides if the report should wait for the complete signal in the javascript | ||
| /// </summary> | ||
| public bool WaitForJavascriptCompletedSignal { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The amount of time a reports javascript can take until it is considered to have timed out | ||
| /// </summary> | ||
| public TimeSpan? WaitForCompletedSignalTimeout { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The default settings for the current report javascript settings | ||
| /// </summary> | ||
| public static BlazorReportCurrentReportJavascriptSettings Default => | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The defaults can be added to each property |
||
| new BlazorReportCurrentReportJavascriptSettings | ||
| { | ||
| WaitForJavascriptCompletedSignal = false, | ||
| WaitForCompletedSignalTimeout = TimeSpan.FromSeconds(3), | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| namespace BlazorReports.Models; | ||
|
|
||
| /// <summary> | ||
| /// | ||
| /// </summary> | ||
| public class BlazorReportGlobalJavascriptSettings | ||
| { | ||
| /// <summary> | ||
| /// The signal that the report is ready | ||
| /// </summary> | ||
| public string ReportIsReadySignal { get; set; } = "reportIsReady"; | ||
|
|
||
| /// <summary> | ||
| /// Decides if the report should wait for the complete signal in the javascript. | ||
| /// (Default: false) | ||
| /// </summary> | ||
| public bool WaitForJavascriptCompletedSignal { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The amount of time a reports javascript can take until it is considered to have timed out | ||
| /// (Default: null) | ||
| /// </summary> | ||
| public TimeSpan WaitForCompletedSignalTimeout { get; set; } = TimeSpan.FromSeconds(3); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,4 +29,9 @@ public class BlazorReportRegistrationOptions | |
| /// Settings for generating a PDF | ||
| /// </summary> | ||
| public BlazorReportsPageSettings PageSettings { get; set; } = new(); | ||
|
|
||
| /// <summary> | ||
| /// Javascript api settings | ||
| /// </summary> | ||
| public BlazorReportCurrentReportJavascriptSettings JavascriptSettings { get; set; } = new(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What other options would be placed here? Thinking about moving the signal and timeout options as a regular setting instead of nesting another object
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As reading it like this, the options don't necesarrily configure Javascript but will just wait for the signal, what do you think?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could either rename the object to JavascriptReportSignalSettings or something along those lines... Solution 1public class JavascriptReportOptions
{
/// <summary>
/// Whether to include the razor.js code-behind file associated with the component (e.g., MyReport.razor.js).
/// </summary>
public bool IncludeCodeBehindScript { get; set; } = true;
/// <summary>
/// Whether to include all global scripts registered in the wwwroot or shared locations for this specific report.
/// </summary>
public bool IncludeGlobalScripts { get; set; } = true;
/// <summary>
/// Settings related to how the JS signals the report is ready.
/// </summary>
public JavascriptReportSignalSettings Signal { get; set; } = new();
/// <summary>
/// Whether to defer script execution until the page is fully loaded so it wouldn't matter if they were placed at the
/// 'head' tag or below the body tag
/// </summary>
public bool DeferScripts { get; set; } = true;
/// <summary>
/// Custom script URLs to include before rendering.
/// </summary>
public List<string> CustomScriptUrls { get; set; } = new();
/// <summary>
/// Custom inline scripts to inject directly into the page.
/// </summary>
public List<string> InlineScripts { get; set; } = new();
}The JavascriptReportOptions would go inside the Solution 2The other option would be to just do public class BlazorReportRegistrationOptions
{
/// <summary>
/// Output format for the report. Defaults to PDF.
/// </summary>
public ReportOutputFormat OutputFormat { get; set; } = ReportOutputFormat.Pdf;
/// <summary>
/// The name of the report. This is utilized to generate the route for the report.
/// </summary>
public string? ReportName { get; set; }
/// <summary>
/// Base styles path for the report.
/// </summary>
public string? BaseStylesPath { get; set; }
/// <summary>
/// Assets path for the report.
/// </summary>
public string? AssetsPath { get; set; }
/// <summary>
/// Settings for generating a PDF
/// </summary>
public BlazorReportsPageSettings PageSettings { get; set; } = new();
// --- JavaScript options defined directly here ---
/// <summary>
/// Whether to include the report's `.razor.js` code-behind file.
/// </summary>
public bool IncludeCodeBehindScript { get; set; } = true;
/// <summary>
/// Whether to include globally registered JS scripts.
/// </summary>
public bool IncludeGlobalScripts { get; set; } = true;
/// <summary>
/// Name of the global JS signal to watch for report readiness.
/// </summary>
public string? ReportIsReadySignal { get; set; }
/// <summary>
/// Whether to wait for the `.completed()` signal from JavaScript.
/// </summary>
public bool WaitForJavascriptCompletedSignal { get; set; } = false;
/// <summary>
/// Timeout to wait for JS `.completed()` before failing.
/// </summary>
public TimeSpan WaitForCompletedSignalTimeout { get; set; } = TimeSpan.FromSeconds(10);
}I think I prefer the first one pero both sound good honestly |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ internal sealed class Browser( | |
| DirectoryInfo dataDirectory, | ||
| Connection connection, | ||
| BlazorReportsBrowserOptions browserOptions, | ||
| BlazorReportGlobalJavascriptSettings globalJavascriptSettings, | ||
| ILogger logger, | ||
| IBrowserPageFactory browserPageFactory | ||
| ) : IAsyncDisposable | ||
|
|
@@ -30,11 +31,18 @@ IBrowserPageFactory browserPageFactory | |
| private readonly SemaphoreSlim _poolLock = new(1, 1); | ||
|
|
||
| public async ValueTask< | ||
| OneOf<Success, ServerBusyProblem, OperationCancelledProblem, BrowserProblem> | ||
| OneOf< | ||
| Success, | ||
| ServerBusyProblem, | ||
| OperationCancelledProblem, | ||
| BrowserProblem, | ||
| CompletedSignalTimeoutProblem | ||
| > | ||
| > GenerateReport( | ||
| PipeWriter pipeWriter, | ||
| string html, | ||
| BlazorReportsPageSettings pageSettings, | ||
| BlazorReportCurrentReportJavascriptSettings currentReportJavascriptSettings, | ||
| CancellationToken cancellationToken | ||
| ) | ||
| { | ||
|
|
@@ -92,6 +100,24 @@ out browserPage | |
| try | ||
| { | ||
| await browserPage.DisplayHtml(html, cancellationToken); | ||
| var shouldReportAwaitJavascript = | ||
andres-m-rodriguez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| currentReportJavascriptSettings.WaitForJavascriptCompletedSignal | ||
| || globalJavascriptSettings.WaitForJavascriptCompletedSignal; | ||
| if (shouldReportAwaitJavascript) | ||
| { | ||
| TimeSpan globalTimeout = globalJavascriptSettings.WaitForCompletedSignalTimeout; | ||
| TimeSpan? currentReportTimeout = | ||
| currentReportJavascriptSettings.WaitForCompletedSignalTimeout; | ||
|
|
||
| TimeSpan timeout = currentReportTimeout ?? globalTimeout; | ||
|
|
||
| var didNotHitTimeOut = await browserPage.WaitForJsFlagAsync(timeout, cancellationToken); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What value do you get out of this variable?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If set to true, it means the report did not hit the timeout—e.g., if the timeout was set 2 seconds and the report finished rendering (including any JavaScript execution) in 1 second, it successfully completed within the allowed time therefore the value will be true (it did not hit the time out). If set to false, it means the report exceeded the timeout—e.g., if it took 3 seconds to finish due to long-running JavaScript or other dependencies, the timeout would have been hit (did not hit the time out will be false). Should I rename the variable? |
||
| if (!didNotHitTimeOut) | ||
| { | ||
| return new CompletedSignalTimeoutProblem(); | ||
| } | ||
| } | ||
|
|
||
| await browserPage.ConvertPageToPdf(pipeWriter, pageSettings, cancellationToken); | ||
| } | ||
| catch (Exception e) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change to blazorReportsSignalReady