Migrate PDF.js to DevExpress DxPdfViewer
Transitioned PDF rendering in `EnvelopeReceiverPage.razor` from `PDF.js` to `DevExpress DxPdfViewer`. Updated code and documentation to reflect the verified API of `DevExpress.Blazor.PdfViewer` v25.2.3, addressing its limitations (e.g., lack of `GoToPageAsync`, `PageNumberChanged`). Implemented `CustomizeToolbar` for navigation/zoom controls and manual state tracking for `_currentPage` and `_viewerZoomLevel`. Replaced JavaScript interop for page count with the `PageCount` property. Retained the custom thumbnail sidebar due to API constraints. Added temporary debug tools for DOM analysis and navigation testing. Updated `TESTING_CHECKLIST.md` and added `DEVEXPRESS_V25_LIMITATIONS.md` to document the new strategy, API limitations, and testing scenarios. Cross-page signature navigation implemented with state updates, though visible page changes remain manual. Prepared for future improvements while ensuring functional migration to `DxPdfViewer`.
This commit is contained in:
233
DEVEXPRESS_V25_LIMITATIONS.md
Normal file
233
DEVEXPRESS_V25_LIMITATIONS.md
Normal file
@@ -0,0 +1,233 @@
|
||||
# DevExpress Blazor PdfViewer v25.2.3 - Verified API Reference
|
||||
|
||||
> **Source:** All information in this document has been verified from the actual source code of `DevExpress.Blazor.PdfViewer` v25.2.3 package.
|
||||
> AI-generated API suggestions (GoToPageAsync, PageNumberChanged, etc.) are NOT real – do not use them.
|
||||
|
||||
---
|
||||
|
||||
## Verified Available Parameters
|
||||
|
||||
| Property | Type | Access | Default | Description |
|
||||
|----------|------|--------|---------|-------------|
|
||||
| `DocumentContent` | `byte[]` | `[Parameter]` GET/SET | – | Feeds PDF content as byte array |
|
||||
| `CssClass` | `string` | `[Parameter]` GET/SET | – | Assigns CSS class to component |
|
||||
| `DocumentName` | `string` | `[Parameter]` GET/SET | `"Document"` | Download filename |
|
||||
| `IsSinglePagePreview` | `bool` | `[Parameter]` GET/SET | `false` | Single page mode |
|
||||
| `SizeMode` | `SizeMode?` | `[Parameter]` GET/SET | `null` | `Small`, `Medium`, `Large` |
|
||||
| `ZoomLevel` | `double` | `[Parameter]` GET/SET | `-1` | **Factor** (not percentage). `1.5` = 150% |
|
||||
| `ActivePageIndex` | `int` | GET only | – | Active page index (0-based). No SET. |
|
||||
| `PageCount` | `int` | GET only | – | Total page count in document |
|
||||
|
||||
---
|
||||
|
||||
## Missing Events (NOT AVAILABLE in v25.2.3)
|
||||
|
||||
- **`PageNumberChanged`** – Not available
|
||||
- **`ZoomLevelChanged`** – Not available
|
||||
- User scrolling or native toolbar page changes do not trigger C# code
|
||||
|
||||
---
|
||||
|
||||
## Missing Properties (NOT AVAILABLE in v25.2.3)
|
||||
|
||||
- **`ToolbarVisible`** – Not available (toolbar cannot be completely hidden)
|
||||
- **`ActivePageIndex` (settable)** – Read-only; no programmatic page navigation
|
||||
|
||||
---
|
||||
|
||||
## Missing Methods (NOT AVAILABLE in v25.2.3)
|
||||
|
||||
- **`GoToPageAsync()`** – Not available
|
||||
- **`GoToNextPageAsync()`** – Not available
|
||||
- **`ZoomAsync()`** – Not available
|
||||
|
||||
---
|
||||
|
||||
## Available Event
|
||||
|
||||
- **`CustomizeToolbar`** – Only available event for toolbar customization
|
||||
|
||||
---
|
||||
|
||||
## Critical Integration Notes
|
||||
|
||||
### ZoomLevel takes factor, not percentage
|
||||
|
||||
```csharp
|
||||
// CORRECT
|
||||
_viewerZoomLevel = 1.5; // viewer displays "150%"
|
||||
_viewerZoomLevel = _currentZoom / 100d; // _currentZoom=150 -> 1.5
|
||||
|
||||
// WRONG
|
||||
_viewerZoomLevel = 150; // viewer displays "15000%"
|
||||
```
|
||||
|
||||
### PageCount replaces JS call
|
||||
|
||||
```csharp
|
||||
// CORRECT - read directly from component (no JS needed)
|
||||
_totalPages = _pdfViewer.PageCount;
|
||||
|
||||
// OLD method (no longer needed for this purpose)
|
||||
// _totalPages = await JSRuntime.InvokeAsync<int>("pdfViewer.getTotalPages");
|
||||
```
|
||||
|
||||
### ActivePageIndex is read-only
|
||||
|
||||
```csharp
|
||||
// CORRECT - read for state synchronization
|
||||
var currentPage = _pdfViewer.ActivePageIndex + 1; // convert to 1-based
|
||||
|
||||
// COMPILE ERROR - no setter
|
||||
// _pdfViewer.ActivePageIndex = 3; // COMPILE ERROR
|
||||
```
|
||||
|
||||
### DocumentContent byte[] feeding
|
||||
|
||||
```razor
|
||||
<DxPdfViewer @ref="_pdfViewer"
|
||||
DocumentContent="@_pdfDocumentContent"
|
||||
ZoomLevel="@_viewerZoomLevel"
|
||||
IsSinglePagePreview="true" />
|
||||
|
||||
@code {
|
||||
DxPdfViewer? _pdfViewer;
|
||||
byte[]? _pdfDocumentContent; // populate in OnInitializedAsync
|
||||
double _viewerZoomLevel = 1.5; // 150%
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact on EnvelopeReceiverPage
|
||||
|
||||
### Features That Don't Work
|
||||
1. **Event-driven overlay updates** – No page/zoom change events
|
||||
2. **Thumbnail click navigation** – Cannot navigate viewer to specific page via C# API
|
||||
3. **Cross-page signature navigation** – No programmatic page change API
|
||||
4. **Automatic overlay synchronization** – User scroll/native toolbar doesn't trigger C#
|
||||
|
||||
### Features That Work
|
||||
1. **ZoomLevel binding** – Custom zoom buttons can update viewer zoom
|
||||
2. **PageCount** – Total pages can be read directly from component
|
||||
3. **IsSinglePagePreview** – Single page mode works
|
||||
4. **DocumentContent** – byte[] feeding works perfectly
|
||||
5. **CustomizeToolbar** – Only way to add custom buttons to toolbar
|
||||
|
||||
---
|
||||
|
||||
## Workaround Strategy
|
||||
|
||||
CustomizeToolbar event is used to add custom navigation/zoom buttons.
|
||||
Manual state tracking (`_currentPage`, `_currentZoom`, `_viewerZoomLevel`) is kept in C#.
|
||||
Overlay refresh is manually triggered only after button clicks.
|
||||
|
||||
```csharp
|
||||
protected void OnCustomizeToolbar(ToolbarModel toolbarModel)
|
||||
{
|
||||
toolbarModel.AllItems.Clear();
|
||||
|
||||
var prevButton = new ToolbarItem
|
||||
{
|
||||
Text = "Previous",
|
||||
IconCssClass = "dx-icon-chevronprev",
|
||||
Enabled = _currentPage > 1,
|
||||
Click = async (args) =>
|
||||
{
|
||||
if (_currentPage > 1)
|
||||
{
|
||||
_currentPage--;
|
||||
_viewerZoomLevel = _currentZoom / 100d;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await RenderSignatureButtonsAsync();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var nextButton = new ToolbarItem
|
||||
{
|
||||
Text = "Next",
|
||||
IconCssClass = "dx-icon-chevronnext",
|
||||
Enabled = _currentPage < _totalPages,
|
||||
Click = async (args) =>
|
||||
{
|
||||
if (_currentPage < _totalPages)
|
||||
{
|
||||
_currentPage++;
|
||||
_viewerZoomLevel = _currentZoom / 100d;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await RenderSignatureButtonsAsync();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var zoomInButton = new ToolbarItem
|
||||
{
|
||||
IconCssClass = "dx-icon-plus",
|
||||
Enabled = _currentZoom < 300,
|
||||
Click = async (args) =>
|
||||
{
|
||||
_currentZoom = Math.Min(_currentZoom + 10, 300);
|
||||
_viewerZoomLevel = _currentZoom / 100d; // 150 -> 1.5
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await RenderSignatureButtonsAsync();
|
||||
}
|
||||
};
|
||||
|
||||
var zoomOutButton = new ToolbarItem
|
||||
{
|
||||
IconCssClass = "dx-icon-minus",
|
||||
Enabled = _currentZoom > 50,
|
||||
Click = async (args) =>
|
||||
{
|
||||
_currentZoom = Math.Max(_currentZoom - 10, 50);
|
||||
_viewerZoomLevel = _currentZoom / 100d; // 150 -> 1.5
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await RenderSignatureButtonsAsync();
|
||||
}
|
||||
};
|
||||
|
||||
toolbarModel.AllItems.Add(prevButton);
|
||||
toolbarModel.AllItems.Add(nextButton);
|
||||
toolbarModel.AllItems.Add(zoomInButton);
|
||||
toolbarModel.AllItems.Add(zoomOutButton);
|
||||
}
|
||||
```
|
||||
|
||||
### PageCount reading example (in OnAfterRenderAsync)
|
||||
|
||||
```csharp
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!_pdfLoaded && _pdfDocumentContent is { Length: > 0 })
|
||||
{
|
||||
await Task.Delay(300); // wait for DxPdfViewer to load
|
||||
|
||||
if (_pdfViewer is not null && _pdfViewer.PageCount > 0)
|
||||
{
|
||||
_totalPages = _pdfViewer.PageCount; // read directly instead of JS
|
||||
_pdfLoaded = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await RenderThumbnailsAsync();
|
||||
await RenderSignatureButtonsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Known Acceptable Limitations
|
||||
|
||||
1. If user scrolls PDF, C# `_currentPage` does not synchronize
|
||||
2. Thumbnail clicks update state but cannot move DevExpress viewer to target page
|
||||
3. Browser zoom gestures do not trigger overlay updates
|
||||
4. Custom toolbar buttons correctly trigger overlay updates
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- DevExpress official documentation: https://docs.devexpress.com/Blazor/DevExpress.Blazor.PdfViewer.DxPdfViewer
|
||||
- Verified package: `DevExpress.Blazor.PdfViewer` v25.2.3
|
||||
- **Note:** AI-suggested APIs (GoToPageAsync, PageNumberChanged, ZoomLevelChanged, ZoomAsync, ToolbarVisible) are NOT real. Do not use.
|
||||
Reference in New Issue
Block a user