From 51e3609cd6449e29e2ff4fb4681708f1fe7156bc Mon Sep 17 00:00:00 2001 From: Major Date: Wed, 8 Sep 2021 08:09:08 +0200 Subject: [PATCH 01/11] ScrollHandler added more Smooth scroll support. --- .github/docs/JsInterop.md | 7 +++---- ...rsoft.Blazor.Components.Common.JsInterop.xml | 11 +++++++---- .../Scroll/IScrollHandler.cs | 11 +++++++---- .../Scroll/ScrollHandler.cs | 12 ++++++------ .../wwwroot/scroll.js | 17 +++++++++++------ .../wwwroot/scroll.min.js | 2 +- .../Shared/MainLayout.razor | 2 +- .../Shared/PageScroll.razor | 4 ++-- .../Shared/MainLayout.razor | 2 +- 9 files changed, 39 insertions(+), 29 deletions(-) diff --git a/.github/docs/JsInterop.md b/.github/docs/JsInterop.md index f677a47d..cc136c92 100644 --- a/.github/docs/JsInterop.md +++ b/.github/docs/JsInterop.md @@ -169,13 +169,12 @@ Exposes a Blazor `ElementReference` of the wrapped around HTML element. It can b - **`DisposeAsync()`: `ValueTask IAsyncDisposable()` interface**
Component implements `IAsyncDisposable` interface Blazor framework components also can `@implements IAsyncDisposable` where the injected service should be Disposed. - ### `IScrollHandler` Functions -- **`ScrollToElementAsync`**: **`Task ScrollToElementAsync(ElementReference elementReference)`**
+- **`ScrollToElementAsync`**: **`Task ScrollToElementAsync(ElementReference elementReference, bool smooth)`**
Scrolls the given element into the page view area. -- **`ScrollToElementByIdAsync`**: **`Task ScrollToElementByIdAsync(string id)`**
+- **`ScrollToElementByIdAsync`**: **`Task ScrollToElementByIdAsync(string id, bool smooth)`**
Finds element by Id and scrolls the given element into the page view area. -- **`ScrollToElementByNameAsync`**: **`Task ScrollToElementByNameAsync(string name)`**
+- **`ScrollToElementByNameAsync`**: **`Task ScrollToElementByNameAsync(string name, bool smooth)`**
Finds element by name and scrolls the given element into the page view area. - **`ScrollToPageEndAsync`**: **`Task ScrollToPageEndAsync(bool smooth)`**
Scrolls to end of the page (X bottom). diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml b/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml index dc12d0cd..955e1d42 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml @@ -732,25 +732,28 @@ Injectable service to handle JS document/window 'scroll' events for the whole document. - + Scrolls the given element into the page view area. Blazor reference to an HTML element + Scroll should jump or smoothly scroll Async Task - + Finds element by Id and scrolls the given element into the page view area. - DOM element id + DOM element id + Scroll should jump or smoothly scroll Async Task - + Finds element by name and scrolls the given element into the page view area. DOM element name + Scroll should jump or smoothly scroll Async Task diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs index a4526af3..7182c004 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs @@ -14,20 +14,23 @@ public interface IScrollHandler : IAsyncDisposable /// Scrolls the given element into the page view area. /// /// Blazor reference to an HTML element + /// Scroll should jump or smoothly scroll /// Async Task - Task ScrollToElementAsync(ElementReference elementReference); + Task ScrollToElementAsync(ElementReference elementReference, bool smooth = false); /// /// Finds element by Id and scrolls the given element into the page view area. /// - /// DOM element id + /// DOM element id + /// Scroll should jump or smoothly scroll /// Async Task - Task ScrollToElementByIdAsync(string id); + Task ScrollToElementByIdAsync(string id, bool smooth = false); /// /// Finds element by name and scrolls the given element into the page view area. /// /// DOM element name + /// Scroll should jump or smoothly scroll /// Async Task - Task ScrollToElementByNameAsync(string name); + Task ScrollToElementByNameAsync(string name, bool smooth = false); /// /// Scrolls to end of the page (X bottom). diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs index 5706773d..af51e72d 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs @@ -54,22 +54,22 @@ public async Task GetPageScrollSizeAsync() return await _scrollJs.InvokeAsync("getPageScrollSize"); } - public async Task ScrollToElementAsync(ElementReference elementReference) + public async Task ScrollToElementAsync(ElementReference elementReference, bool smooth) { await CheckJsObjectAsync(); - await _scrollJs.InvokeVoidAsync("scrollToElement", elementReference); + await _scrollJs.InvokeVoidAsync("scrollToElement", elementReference, smooth); } - public async Task ScrollToElementByIdAsync(string id) + public async Task ScrollToElementByIdAsync(string id, bool smooth) { await CheckJsObjectAsync(); - await _scrollJs.InvokeVoidAsync("scrollToElementById", id); + await _scrollJs.InvokeVoidAsync("scrollToElementById", id, smooth); } - public async Task ScrollToElementByNameAsync(string name) + public async Task ScrollToElementByNameAsync(string name, bool smooth) { await CheckJsObjectAsync(); - await _scrollJs.InvokeVoidAsync("scrollToElementByName", name); + await _scrollJs.InvokeVoidAsync("scrollToElementByName", name, smooth); } public async Task RegisterPageScrollAsync(Func scrollCallback) diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js index f412eff2..75a941da 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js @@ -1,20 +1,25 @@ /* Extensions */ //Element scrolled to page top -export function scrollToElement(element) { +export function scrollToElement(element, smooth) { if (element && typeof element.scrollIntoView === "function") { - element.scrollIntoView(); + if (smooth) { + element.scrollIntoView({ behavior: 'smooth' }); + } + else { + element.scrollIntoView(); + } } } -export function scrollToElementById(id) { +export function scrollToElementById(id, smooth) { if (id) { - scrollToElement(document.getElementById(id)); + scrollToElement(document.getElementById(id), smooth); } } -export function scrollToElementByName(name) { +export function scrollToElementByName(name, smooth) { if (name) { let elements = document.getElementsByName(name) if (elements && elements.length > 0) { - scrollToElement(elements[0]); + scrollToElement(elements[0], smooth); } } } diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.min.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.min.js index 2e9795e3..efc4a1dc 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.min.js +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.min.js @@ -1 +1 @@ -export function scrollToElement(n){n&&typeof n.scrollIntoView=="function"&&n.scrollIntoView()}export function scrollToElementById(n){n&&scrollToElement(document.getElementById(n))}export function scrollToElementByName(n){if(n){let t=document.getElementsByName(n);t&&t.length>0&&scrollToElement(t[0])}}export function scrollToElementInParent(t,i){t&&i&&typeof t.scrollTop!==n&&isElementHidden(i)&&(t.scrollTop=i.offsetHeight)}export function scrollInParentById(t,i){if(t&&i&&typeof t.scrollTop!==n){let n=document.getElementById(i);n&&isElementHidden(n)&&(t.scrollTop=n.offsetHeight)}}export function scrollInParentByClass(t,i){if(t&&i&&typeof t.scrollTop!==n){let n=document.getElementsByClassName(i);if(n&&n[0]){let i=n[0];isElementHiddenBelow(i)?(t.scrollTop+=n[0].offsetHeight,isElementHiddenBelow(i)&&(t.scrollTop=i.offsetTop-t.clientHeight+i.offsetHeight)):isElementHiddenAbove(i)&&(t.scrollTop-=n[0].offsetHeight,isElementHiddenAbove(i)&&(t.scrollTop=i.offsetTop))}}}export function isElementHidden(n){return isElementHiddenBelow(n)||isElementHiddenAbove(n)}export function isElementHiddenBelow(n){return!n||!n.offsetParent?!1:n.offsetHeight+n.offsetTop>n.offsetParent.scrollTop+n.offsetParent.clientHeight}export function isElementHiddenAbove(n){return!n||!n.offsetParent?!1:n.offsetTop=document.body.offsetHeight}}export function getPageScrollSize(){return{X:document.body.scrollWidth,Y:document.body.scrollHeight,IsPageTop:window.scrollY==0,IsPageBottom:window.innerHeight+window.scrollY>=document.body.offsetHeight}}function i(n){return function(){let t=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,r={X:i,Y:t};n.invokeMethodAsync("PageScroll",r)}}function r(n,t,i){let r=!1;for(let i=0;i0&&scrollToElement(i[0],t)}}export function scrollToElementInParent(t,i){t&&i&&typeof t.scrollTop!==n&&isElementHidden(i)&&(t.scrollTop=i.offsetHeight)}export function scrollInParentById(t,i){if(t&&i&&typeof t.scrollTop!==n){let n=document.getElementById(i);n&&isElementHidden(n)&&(t.scrollTop=n.offsetHeight)}}export function scrollInParentByClass(t,i){if(t&&i&&typeof t.scrollTop!==n){let n=document.getElementsByClassName(i);if(n&&n[0]){let i=n[0];isElementHiddenBelow(i)?(t.scrollTop+=n[0].offsetHeight,isElementHiddenBelow(i)&&(t.scrollTop=i.offsetTop-t.clientHeight+i.offsetHeight)):isElementHiddenAbove(i)&&(t.scrollTop-=n[0].offsetHeight,isElementHiddenAbove(i)&&(t.scrollTop=i.offsetTop))}}}export function isElementHidden(n){return isElementHiddenBelow(n)||isElementHiddenAbove(n)}export function isElementHiddenBelow(n){return!n||!n.offsetParent?!1:n.offsetHeight+n.offsetTop>n.offsetParent.scrollTop+n.offsetParent.clientHeight}export function isElementHiddenAbove(n){return!n||!n.offsetParent?!1:n.offsetTop=document.body.offsetHeight}}export function getPageScrollSize(){return{X:document.body.scrollWidth,Y:document.body.scrollHeight,IsPageTop:window.scrollY==0,IsPageBottom:window.innerHeight+window.scrollY>=document.body.offsetHeight}}function i(n){return function(){let t=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,r={X:i,Y:t};n.invokeMethodAsync("PageScroll",r)}}function r(n,t,i){let r=!1;for(let i=0;i + @* Google Analytics initialize*@ @using Majorsoft.Blazor.Extensions.Analytics.Google diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor index 64dd8bb4..a12bcc92 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor @@ -2,7 +2,7 @@ @if (ShowScrollToBottom) { - +
@@ -11,7 +11,7 @@ } - +
diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor index 62509ebb..ffdada27 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor @@ -20,7 +20,7 @@ @*Permalink initialize*@ @using Majorsoft.Blazor.Components.PermaLink - + @*Server hosted Blazor console log*@ @using Majorsoft.Blazor.Server.Logging.Console From 3f45335d050b588ddc9623d9757cb00e6c390382 Mon Sep 17 00:00:00 2001 From: Major Date: Wed, 8 Sep 2021 14:57:13 +0200 Subject: [PATCH 02/11] Added more smooth scroll, bugfixes and demo updates. --- .github/docs/JsInterop.md | 34 +++++++--- ...oft.Blazor.Components.Common.JsInterop.xml | 42 +++++++++--- .../ElementReferenceScrollExtensions.cs | 65 +++++++++++++++---- .../Scroll/IScrollHandler.cs | 6 +- .../wwwroot/scroll.js | 37 ++++++++--- .../wwwroot/scroll.min.js | 2 +- .../Shared/MainLayout.razor | 2 +- .../Components/JsDemo/BrowserDateJs.razor | 2 +- .../Components/JsDemo/ScrollJs.razor | 14 ++-- .../Shared/MainLayout.razor | 2 +- 10 files changed, 154 insertions(+), 52 deletions(-) diff --git a/.github/docs/JsInterop.md b/.github/docs/JsInterop.md index cc136c92..7801a18e 100644 --- a/.github/docs/JsInterop.md +++ b/.github/docs/JsInterop.md @@ -171,11 +171,11 @@ Component implements `IAsyncDisposable` interface Blazor framework components al ### `IScrollHandler` Functions - **`ScrollToElementAsync`**: **`Task ScrollToElementAsync(ElementReference elementReference, bool smooth)`**
-Scrolls the given element into the page view area. +Scrolls the given element into the page view area. **Note: smooth scroll on element level might not supported by all browsers.** - **`ScrollToElementByIdAsync`**: **`Task ScrollToElementByIdAsync(string id, bool smooth)`**
-Finds element by Id and scrolls the given element into the page view area. +Finds element by Id and scrolls the given element into the page view area. **Note: smooth scroll on element level might not supported by all browsers.** - **`ScrollToElementByNameAsync`**: **`Task ScrollToElementByNameAsync(string name, bool smooth)`**
-Finds element by name and scrolls the given element into the page view area. +Finds element by name and scrolls the given element into the page view area. **Note: smooth scroll on element level might not supported by all browsers.** - **`ScrollToPageEndAsync`**: **`Task ScrollToPageEndAsync(bool smooth)`**
Scrolls to end of the page (X bottom). - **`ScrollToPageTopAsync`**: **`Task ScrollToPageTopAsync(bool smooth)`**
@@ -196,18 +196,34 @@ Removes event listener for 'scroll' HTML event for the whole document/window by Implements `IAsyncDisposable` interface the injected service should be Disposed. ### `ElementReference` extensions -- **`ScrollToElementAsync`**: **`Task ScrollToElementAsync(this ElementReference elementReference)`**
-- **`ScrollToEndAsync`**: **`Task ScrollToEndAsync(this ElementReference elementReference)`**
-- **`ScrollToTopAsync`**: **`Task ScrollToTopAsync(this ElementReference elementReference)`**
-- **`ScrollToXAsync`**: **`Task ScrollToXAsync(this ElementReference elementReference, double xPos)`**
-- **`ScrollToYAsync`**: **`Task ScrollToYAsync(this ElementReference elementReference, double yPos)`**
-- **`GetScrollPositionAsync`**: **`Task GetScrollPositionAsync(this ElementReference elementReference)`**
+- **`ScrollToElementAsync`**: **`Task ScrollToElementAsync(this ElementReference elementReference, bool smooth = false)`**
+ Scrolls HTML page to given element. **Note: smooth scroll on element level might not supported by all browsers.** +- **`ScrollToEndAsync`**: **`Task ScrollToEndAsync(this ElementReference elementReference, bool smooth = false)`**
+Scrolls inside the given element to the bottom (end). **Note: smooth scroll on element level might not supported by all browsers.** +- **`ScrollToTopAsync`**: **`Task ScrollToTopAsync(this ElementReference elementReference, bool smooth = false)`**
+Scrolls inside the given element to the beginning (top). **Note: smooth scroll on element level might not supported by all browsers.** +- **`ScrollToXAsync`**: **`Task ScrollToXAsync(this ElementReference elementReference, double xPos, bool smooth = false)`**
+Scrolls inside the given element to the given X position. **Note: smooth scroll on element level might not supported by all browsers.** +- **`ScrollToYAsync`**: **`Task ScrollToYAsync(this ElementReference elementReference, double yPos, bool smooth = false)`**
+Scrolls inside the given element to the given Y position. **Note: smooth scroll on element level might not supported by all browsers.** +- **`ScrollToAsync`**: **`Task ScrollToYAsync(this ElementReference elementReference, double xPos, double yPos, bool smooth = false)`**
+Scrolls inside the given element to the given X and Y positions. **Note: smooth scroll on element level might not supported by all browsers.** +- **`GetScrollXPositionAsync`**: **`Task GetScrollXPositionAsync(this ElementReference elementReference)`**
+Returns given element scroll X (left) position. +- **`GetScrollYPositionAsync`**: **`Task GetScrollYPositionAsync(this ElementReference elementReference)`**
+Returns given element scroll Y (top) position. - **`IsElementHiddenAsync`**: **`Task IsElementHiddenAsync(this ElementReference elementReference)`**
+Returns given element is visible on HTML document or not. - **`IsElementHiddenBelowAsync`**: **`Task IsElementHiddenBelowAsync(this ElementReference elementReference)`**
+Returns given element is below of the view port. - **`IsElementHiddenAboveAsync`**: **`Task IsElementHiddenAboveAsync(this ElementReference elementReference)`**
+Returns given element is above of the view port. - **`ScrollToElementInParentAsync`**: **`Task ScrollToElementInParentAsync(this ElementReference parent, ElementReference innerElement)`**
+Scrolls inside the given parent element to the given inner element. - **`ScrollInParentByIdAsync`**: **`Task ScrollInParentByIdAsync(this ElementReference parent, string id)`**
+Scrolls inside the given parent element to the given inner element by Id. - **`ScrollInParentByClassAsync`**: **`Task ScrollInParentByClassAsync(this ElementReference parent, string className)`**
+Scrolls inside the given parent element to the given first found inner element by class name. ## Resize JS (See: [demo app](https://blazorextensions.z6.web.core.windows.net/jsinterop#resize-js)) **Resize JS** is an **injectable `IResizeHandler` service** for Window (global) and HTML Elements resize event callback handlers. diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml b/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml index 955e1d42..8407ba14 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml @@ -638,50 +638,72 @@ Extensions for HTML elements.
- + - Scrolls HTML page to given element + Scrolls HTML page to given element. Blazor reference to an HTML element + Scroll should jump or smoothly scroll Note: might not all browsers support it Async Task - + Scrolls inside the given element to the bottom (end). Blazor reference to an HTML element + Scroll should jump or smoothly scroll Note: might not all browsers support it Async Task - + Scrolls inside the given element to the beginning (top). Blazor reference to an HTML element + Scroll should jump or smoothly scroll Note: might not all browsers support it Async Task - + Scrolls inside the given element to the given X position. Blazor reference to an HTML element Scroll X position + Scroll should jump or smoothly scroll Note: might not all browsers support it Async Task - + Scrolls inside the given element to the given Y position. Blazor reference to an HTML element Scroll Y position + Scroll should jump or smoothly scroll Note: might not all browsers support it + Async Task + + + + Scrolls inside the given element to the given X and Y positions. + + Blazor reference to an HTML element + Scroll X position + Scroll Y position + Scroll should jump or smoothly scroll Note: might not all browsers support it Async Task - Returns given element scroll X position. + Returns given element scroll X (left) position. Blazor reference to an HTML element Async Task with X pos + + + Returns given element scroll Y (top) position. + + Blazor reference to an HTML element + Async Task with Y pos + Returns given element is visible on HTML document or not. @@ -737,7 +759,7 @@ Scrolls the given element into the page view area. Blazor reference to an HTML element - Scroll should jump or smoothly scroll + Scroll should jump or smoothly scroll Note: might not all browsers support it Async Task @@ -745,7 +767,7 @@ Finds element by Id and scrolls the given element into the page view area. DOM element id - Scroll should jump or smoothly scroll + Scroll should jump or smoothly scroll Note: might not all browsers support it Async Task @@ -753,7 +775,7 @@ Finds element by name and scrolls the given element into the page view area. DOM element name - Scroll should jump or smoothly scroll + Scroll should jump or smoothly scroll Note: might not all browsers support it Async Task diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ElementReferenceScrollExtensions.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ElementReferenceScrollExtensions.cs index 4531d286..addbfe7f 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ElementReferenceScrollExtensions.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ElementReferenceScrollExtensions.cs @@ -12,17 +12,18 @@ namespace Majorsoft.Blazor.Components.Common.JsInterop.Scroll public static class ElementReferenceScrollExtensions { /// - /// Scrolls HTML page to given element + /// Scrolls HTML page to given element. /// /// Blazor reference to an HTML element + /// Scroll should jump or smoothly scroll Note: might not all browsers support it /// Async Task - public static async Task ScrollToElementAsync(this ElementReference elementReference) + public static async Task ScrollToElementAsync(this ElementReference elementReference, bool smooth = false) { await using (var module = await elementReference.GetJsObject()) { if (module is not null) { - await module.InvokeVoidAsync("scrollToElement", elementReference); + await module.InvokeVoidAsync("scrollToElement", elementReference, smooth); } } } @@ -31,14 +32,15 @@ public static async Task ScrollToElementAsync(this ElementReference elementRefer /// Scrolls inside the given element to the bottom (end). /// /// Blazor reference to an HTML element + /// Scroll should jump or smoothly scroll Note: might not all browsers support it /// Async Task - public static async Task ScrollToEndAsync(this ElementReference elementReference) + public static async Task ScrollToEndAsync(this ElementReference elementReference, bool smooth = false) { await using (var module = await elementReference.GetJsObject()) { if (module is not null) { - await module.InvokeVoidAsync("scrollToEnd", elementReference); + await module.InvokeVoidAsync("scrollToEnd", elementReference, smooth); } } } @@ -47,14 +49,15 @@ public static async Task ScrollToEndAsync(this ElementReference elementReference /// Scrolls inside the given element to the beginning (top). /// /// Blazor reference to an HTML element + /// Scroll should jump or smoothly scroll Note: might not all browsers support it /// Async Task - public static async Task ScrollToTopAsync(this ElementReference elementReference) + public static async Task ScrollToTopAsync(this ElementReference elementReference, bool smooth = false) { await using (var module = await elementReference.GetJsObject()) { if (module is not null) { - await module.InvokeVoidAsync("scrollToTop", elementReference); + await module.InvokeVoidAsync("scrollToTop", elementReference, smooth); } } } @@ -64,14 +67,15 @@ public static async Task ScrollToTopAsync(this ElementReference elementReference /// /// Blazor reference to an HTML element /// Scroll X position + /// Scroll should jump or smoothly scroll Note: might not all browsers support it /// Async Task - public static async Task ScrollToXAsync(this ElementReference elementReference, double xPos) + public static async Task ScrollToXAsync(this ElementReference elementReference, double xPos, bool smooth = false) { await using (var module = await elementReference.GetJsObject()) { if (module is not null) { - await module.InvokeVoidAsync("scrollToX", elementReference, xPos); + await module.InvokeVoidAsync("scrollToX", elementReference, xPos, smooth); } } } @@ -81,20 +85,40 @@ public static async Task ScrollToXAsync(this ElementReference elementReference, /// /// Blazor reference to an HTML element /// Scroll Y position + /// Scroll should jump or smoothly scroll Note: might not all browsers support it /// Async Task - public static async Task ScrollToYAsync(this ElementReference elementReference, double yPos) + public static async Task ScrollToYAsync(this ElementReference elementReference, double yPos, bool smooth = false) { await using (var module = await elementReference.GetJsObject()) { if (module is not null) { - await module.InvokeVoidAsync("scrollToY", elementReference, yPos); + await module.InvokeVoidAsync("scrollToY", elementReference, yPos, smooth); } } } /// - /// Returns given element scroll X position. + /// Scrolls inside the given element to the given X and Y positions. + /// + /// Blazor reference to an HTML element + /// Scroll X position + /// Scroll Y position + /// Scroll should jump or smoothly scroll Note: might not all browsers support it + /// Async Task + public static async Task ScrollToAsync(this ElementReference elementReference, double xPos, double yPos, bool smooth = false) + { + await using (var module = await elementReference.GetJsObject()) + { + if (module is not null) + { + await module.InvokeVoidAsync("scrollTo", elementReference, xPos, yPos, smooth); + } + } + } + + /// + /// Returns given element scroll X (left) position. /// /// Blazor reference to an HTML element /// Async Task with X pos @@ -110,6 +134,23 @@ public static async Task GetScrollXPositionAsync(this ElementReference e return 0; } + /// + /// Returns given element scroll Y (top) position. + /// + /// Blazor reference to an HTML element + /// Async Task with Y pos + public static async Task GetScrollYPositionAsync(this ElementReference elementReference) + { + await using (var module = await elementReference.GetJsObject()) + { + if (module is not null) + { + return await module.InvokeAsync("getScrollYPosition", elementReference); + } + } + + return 0; + } /// /// Returns given element is visible on HTML document or not. diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs index 7182c004..41ad231a 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs @@ -14,21 +14,21 @@ public interface IScrollHandler : IAsyncDisposable /// Scrolls the given element into the page view area. /// /// Blazor reference to an HTML element - /// Scroll should jump or smoothly scroll + /// Scroll should jump or smoothly scroll Note: might not all browsers support it /// Async Task Task ScrollToElementAsync(ElementReference elementReference, bool smooth = false); /// /// Finds element by Id and scrolls the given element into the page view area. /// /// DOM element id - /// Scroll should jump or smoothly scroll + /// Scroll should jump or smoothly scroll Note: might not all browsers support it /// Async Task Task ScrollToElementByIdAsync(string id, bool smooth = false); /// /// Finds element by name and scrolls the given element into the page view area. /// /// DOM element name - /// Scroll should jump or smoothly scroll + /// Scroll should jump or smoothly scroll Note: might not all browsers support it /// Async Task Task ScrollToElementByNameAsync(string name, bool smooth = false); diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js index 75a941da..089b04de 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js @@ -80,27 +80,46 @@ export function isElementHiddenAbove(element) { } //Scrolling inside an element use it for e.g. Textarea -export function scrollToEnd(element) { +export function scrollToEnd(element, smooth) { if (element && typeof element.scrollTop !== undef && typeof element.scrollHeight !== undef) { - element.scrollTop = element.scrollHeight; + scrollTo(element, element.scrollLeft, element.scrollHeight, smooth); } } -export function scrollToTop(element) { +export function scrollToTop(element, smooth) { if (element && typeof element.scrollTop !== undef) { - element.scrollTop = 0; + scrollTo(element, element.scrollLeft, 0, smooth); } } -export function scrollToX(element, x) { +export function scrollToX(element, x, smooth) { if (element && typeof element.scrollTop !== undef) { - element.scrollTop = x; + scrollTo(element, x, element.scrollTop, smooth); } } -export function scrollToY(element, y) { +export function scrollToY(element, y, smooth) { if (element && typeof element.scrollLeft !== undef) { - element.scrollLeft = y; + scrollTo(element, element.scrollLeft, y, smooth); + } +} +export function scrollTo(element, x, y, smooth) { + if (element) { + if (smooth) { + element.scroll({ + top: y, + left: x, + behavior: 'smooth'}); + } + else { + element.scrollTop = y; + element.scrollLeft = x; + } } } export function getScrollXPosition(element) { + if (element && typeof element.scrollLeft !== undef) { + return element.scrollLeft; + } +} +export function getScrollYPosition(element) { if (element && typeof element.scrollTop !== undef) { return element.scrollTop; } @@ -250,4 +269,4 @@ export function dispose(eventIdArray) { removeScrollEvent(eventIdArray[i]); } } -} +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.min.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.min.js index efc4a1dc..2fd4b7c2 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.min.js +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.min.js @@ -1 +1 @@ -export function scrollToElement(n,t){n&&typeof n.scrollIntoView=="function"&&(t?n.scrollIntoView({behavior:"smooth"}):n.scrollIntoView())}export function scrollToElementById(n,t){n&&scrollToElement(document.getElementById(n),t)}export function scrollToElementByName(n,t){if(n){let i=document.getElementsByName(n);i&&i.length>0&&scrollToElement(i[0],t)}}export function scrollToElementInParent(t,i){t&&i&&typeof t.scrollTop!==n&&isElementHidden(i)&&(t.scrollTop=i.offsetHeight)}export function scrollInParentById(t,i){if(t&&i&&typeof t.scrollTop!==n){let n=document.getElementById(i);n&&isElementHidden(n)&&(t.scrollTop=n.offsetHeight)}}export function scrollInParentByClass(t,i){if(t&&i&&typeof t.scrollTop!==n){let n=document.getElementsByClassName(i);if(n&&n[0]){let i=n[0];isElementHiddenBelow(i)?(t.scrollTop+=n[0].offsetHeight,isElementHiddenBelow(i)&&(t.scrollTop=i.offsetTop-t.clientHeight+i.offsetHeight)):isElementHiddenAbove(i)&&(t.scrollTop-=n[0].offsetHeight,isElementHiddenAbove(i)&&(t.scrollTop=i.offsetTop))}}}export function isElementHidden(n){return isElementHiddenBelow(n)||isElementHiddenAbove(n)}export function isElementHiddenBelow(n){return!n||!n.offsetParent?!1:n.offsetHeight+n.offsetTop>n.offsetParent.scrollTop+n.offsetParent.clientHeight}export function isElementHiddenAbove(n){return!n||!n.offsetParent?!1:n.offsetTop=document.body.offsetHeight}}export function getPageScrollSize(){return{X:document.body.scrollWidth,Y:document.body.scrollHeight,IsPageTop:window.scrollY==0,IsPageBottom:window.innerHeight+window.scrollY>=document.body.offsetHeight}}function i(n){return function(){let t=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,r={X:i,Y:t};n.invokeMethodAsync("PageScroll",r)}}function r(n,t,i){let r=!1;for(let i=0;i0&&scrollToElement(i[0],t)}}export function scrollToElementInParent(t,i){t&&i&&typeof t.scrollTop!==n&&isElementHidden(i)&&(t.scrollTop=i.offsetHeight)}export function scrollInParentById(t,i){if(t&&i&&typeof t.scrollTop!==n){let n=document.getElementById(i);n&&isElementHidden(n)&&(t.scrollTop=n.offsetHeight)}}export function scrollInParentByClass(t,i){if(t&&i&&typeof t.scrollTop!==n){let n=document.getElementsByClassName(i);if(n&&n[0]){let i=n[0];isElementHiddenBelow(i)?(t.scrollTop+=n[0].offsetHeight,isElementHiddenBelow(i)&&(t.scrollTop=i.offsetTop-t.clientHeight+i.offsetHeight)):isElementHiddenAbove(i)&&(t.scrollTop-=n[0].offsetHeight,isElementHiddenAbove(i)&&(t.scrollTop=i.offsetTop))}}}export function isElementHidden(n){return isElementHiddenBelow(n)||isElementHiddenAbove(n)}export function isElementHiddenBelow(n){return!n||!n.offsetParent?!1:n.offsetHeight+n.offsetTop>n.offsetParent.scrollTop+n.offsetParent.clientHeight}export function isElementHiddenAbove(n){return!n||!n.offsetParent?!1:n.offsetTop=document.body.offsetHeight}}export function getPageScrollSize(){return{X:document.body.scrollWidth,Y:document.body.scrollHeight,IsPageTop:window.scrollY==0,IsPageBottom:window.innerHeight+window.scrollY>=document.body.offsetHeight}}function i(n){return function(){let t=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,r={X:i,Y:t};n.invokeMethodAsync("PageScroll",r)}}function r(n,t,i){let r=!1;for(let i=0;i + @* Google Analytics initialize*@ @using Majorsoft.Blazor.Extensions.Analytics.Google diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserDateJs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserDateJs.razor index 038dc059..2e51f130 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserDateJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserDateJs.razor @@ -5,7 +5,7 @@ Browser Date JS is an injectable IBrowserDateService service simple JS call to new Date(); to retrieve client machine date and time. - +

Browser date time: @_date

diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor index 2eaf9dc5..94301dfc 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor @@ -40,6 +40,7 @@
+ Smooth scroll to View (might not supported by this Browser): @@ -103,11 +104,11 @@

- - + Smooth scroll in Textbox: + +
-
@@ -125,11 +126,14 @@ await ScrollEventHandler(); } + private bool _smoothScrollInTextbox = true; + //Scroll examples private ElementReference _scrollToView; + private bool _smoothScrollToView; private async Task ScrollToView() { - await _scrollToView.ScrollToElementAsync(); + await _scrollToView.ScrollToElementAsync(_smoothScrollToView); } private ElementReference _log; @@ -139,7 +143,7 @@ _logMessage += $"{DateTime.Now.TimeOfDay}: Page scrolled: X pos: {args.X}, Y pos: {args.Y}{Environment.NewLine}"; StateHasChanged(); - await _log.ScrollToEndAsync(); + await _log.ScrollToEndAsync(smooth: true); } private bool _scrollSubscribed = false; diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor index ffdada27..ddd63464 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor @@ -20,7 +20,7 @@ @*Permalink initialize*@ @using Majorsoft.Blazor.Components.PermaLink - + @*Server hosted Blazor console log*@ @using Majorsoft.Blazor.Server.Logging.Console From 1dd25734a9a4cea6f5996370b59cfe3be26b0c69 Mon Sep 17 00:00:00 2001 From: Major Date: Wed, 8 Sep 2021 17:22:04 +0200 Subject: [PATCH 03/11] Permalink service updates added event for perma value detection and update link. --- .github/docs/PermaLink.md | 18 ++++- .../IPermaLinkWatcherService.cs | 18 +++++ .../Majorsoft.Blazor.Components.PermaLink.xml | 52 ++++++++++++++ .../PermaLinkBlazorServerInitializer.razor | 10 +++ .../PermaLinkWatcherService.cs | 72 +++++++++++++++++-- .../PermalinkBlazorWasmInitializer.razor | 11 +++ .../PermalinkDetectedEventArgs.cs | 33 +++++++++ .../Components/Permalink.razor | 5 +- 8 files changed, 210 insertions(+), 9 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.PermaLink/PermalinkDetectedEventArgs.cs diff --git a/.github/docs/PermaLink.md b/.github/docs/PermaLink.md index 5e94be02..81ef2520 100644 --- a/.github/docs/PermaLink.md +++ b/.github/docs/PermaLink.md @@ -32,9 +32,15 @@ For more details see Usage section. ### Functions - **`WatchPermaLinks()`**: **`void WatchPermaLinks()`**
Starts a navigation watcher which will check for Permalinks in the URLs. +- **`ChangePermalink()`**: **`void ChangePermalink(string? newPermalink, bool doNotNavigate)`**
+Modify the current URL with given new peralink value and trigger navigation or just update browser History. - **`Dispose()`: `@implements IDisposable` interface**
Component implements `IDisposable` interface Blazor framework will call it when parent removed from render tree. +### Events +- **`PermalinkDetected`: `event EventHandler**
+Event handler for parmalinks detected on navigation. + ## `PermaLinkElement` component ### Properties @@ -68,9 +74,17 @@ Callback function called when Permalink icon clicked and **`PermaLinkIconActions ## PermaLinkBlazorServerInitializer Available from v1.4.0 It is convenient wrapper component to initialize navigation watcher in your Blazor Server App `MainLayout.razor` page. **Only one Initializer component allowed per Application.** +### Properties +- **`SmootScroll`: `bool { get; set; }` (default: false)**
+Scroll should be jump or smoothly scroll. **Note: smooth scroll on element level might not supported by all browsers.** + ## PermalinkBlazorWasmInitializer Available from v1.4.0 It is convenient wrapper component to initialize navigation watcher in your Blazor WebAssembly App `MainLayout.razor` page. **Only one Initializer component allowed per Application.** +### Properties +- **`SmootScroll`: `bool { get; set; }` (default: false)**
+Scroll should be jump or smoothly scroll. **Note: smooth scroll on element level might not supported by all browsers.** + # Configuration ## Installation @@ -141,7 +155,7 @@ Also instance should be disposed. ``` @*Permalink initialize*@ @using Majorsoft.Blazor.Components.PermaLink - + ``` #### Server hosted projects @@ -208,7 +222,7 @@ It has to be instantiated manually by using the following code. Also instance sh ``` @*Permalink initialize*@ @using Majorsoft.Blazor.Components.PermaLink - + ``` #### Creating permalink (#) navigation points inside a Blazor page diff --git a/src/Majorsoft.Blazor.Components.PermaLink/IPermaLinkWatcherService.cs b/src/Majorsoft.Blazor.Components.PermaLink/IPermaLinkWatcherService.cs index 918100aa..b5fb1f1a 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/IPermaLinkWatcherService.cs +++ b/src/Majorsoft.Blazor.Components.PermaLink/IPermaLinkWatcherService.cs @@ -8,6 +8,24 @@ namespace Majorsoft.Blazor.Components.PermaLink /// public interface IPermaLinkWatcherService : IDisposable { + /// + /// Should scroll smoothly to the found permalink element or jump. + /// Note: smooth scroll on element level might not supported by all Browsers. + /// + bool SmoothScroll { get; set; } + + /// + /// Event handler for parmalinks detected on navigation. + /// + public event EventHandler PermalinkDetected; + + /// + /// Modify the current URL with given new peralink value and trigger navigation or just update browser History. + /// + /// New peramlink value, NULL will remove permalink part from URL + /// False, will trigger a navigation. When true, just add new URL to the History + void ChangePermalink(string? newPermalink, bool doNotNavigate); + /// /// Starts a navigation watcher which will check for Permalinks in the URLs. /// diff --git a/src/Majorsoft.Blazor.Components.PermaLink/Majorsoft.Blazor.Components.PermaLink.xml b/src/Majorsoft.Blazor.Components.PermaLink/Majorsoft.Blazor.Components.PermaLink.xml index bba85b73..d2e0f8fa 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/Majorsoft.Blazor.Components.PermaLink.xml +++ b/src/Majorsoft.Blazor.Components.PermaLink/Majorsoft.Blazor.Components.PermaLink.xml @@ -10,11 +10,51 @@ and should be injected only once for the whole application. Best way to use MainLayout.razor.
+ + + Should scroll smoothly to the found permalink element or jump. + Note: smooth scroll on element level might not supported by all Browsers. + + + + + Event handler for parmalinks detected on navigation. + + + + + Modify the current URL with given new peralink value and trigger navigation or just update browser History. + + New peramlink value, NULL will remove permalink part from URL + False, will trigger a navigation. When true, just add new URL to the History + Starts a navigation watcher which will check for Permalinks in the URLs. + + + Event arguments for PermalinkDetected event. + + + + + System.EventArgs for Microsoft.AspNetCore.Components.NavigationManager.LocationChanged. + + + + + Detected permalink value. + + + + + Default constructor + + NavigationManager event + Detected permalink value + Extension methods to register required Permalink services into IServiceCollection @@ -38,6 +78,18 @@ Implementation of + + + Gets or sets if peramlink should be scrolled smoothly into view or not. + Note: smooth scroll on element level might not supported by all Browsers. + + + + + Gets or sets if peramlink should be scrolled smoothly into view or not. + Note: smooth scroll on element level might not supported by all Browsers. + + Map HTML container Id. It can be used when multiple Permalinks added to one page. diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor index 6ae21bee..d2c0bf84 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor +++ b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor @@ -31,6 +31,16 @@ } } + /// + /// Gets or sets if peramlink should be scrolled smoothly into view or not. + /// Note: smooth scroll on element level might not supported by all Browsers. + /// + [Parameter] public bool SmoothScroll + { + get => _permalinkWatcher.SmoothScroll; + set { _permalinkWatcher.SmoothScroll = value; } + } + public async ValueTask DisposeAsync() { if (_scrollHandler is not null) diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkWatcherService.cs b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkWatcherService.cs index b11fdeb8..929cdf7c 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkWatcherService.cs +++ b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkWatcherService.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using System; +using System.Text.RegularExpressions; using Majorsoft.Blazor.Components.Common.JsInterop.Scroll; @@ -19,11 +20,17 @@ public class PermaLinkWatcherService : IPermaLinkWatcherService private readonly NavigationManager _navigationManager; private readonly ILogger _logger; - public PermaLinkWatcherService(IScrollHandler scrollHandler, NavigationManager navigationManager, ILogger logger) + public event EventHandler PermalinkDetected; + + public bool SmoothScroll { get; set; } + + public PermaLinkWatcherService(IScrollHandler scrollHandler, NavigationManager navigationManager, ILogger logger, bool smoothScroll = false) { _scrollHandler = scrollHandler; _navigationManager = navigationManager; _logger = logger; + + SmoothScroll = smoothScroll; } public void WatchPermaLinks() @@ -39,14 +46,67 @@ public void WatchPermaLinks() private void HandleLocationChanged(object sender, LocationChangedEventArgs e) { _logger.LogDebug($"{nameof(PermaLinkWatcherService)} - {nameof(HandleLocationChanged)}: navigation happened new URL: {e.Location}"); - var match = _poundRegex.Match(e.Location); + var perma = DetectPermalink(e.Location); - if(match.Success && match.Groups.Count == 2) + if(!string.IsNullOrWhiteSpace(perma)) { - var perma = match.Groups[1].Value; _logger.LogDebug($"{nameof(PermaLinkWatcherService)} - {nameof(HandleLocationChanged)}: PermaLink found: {perma}"); - _scrollHandler.ScrollToElementByNameAsync(perma); + + if(PermalinkDetected is not null) + { + PermalinkDetected.Invoke(this, new PermalinkDetectedEventArgs(e, perma)); + } + + _scrollHandler.ScrollToElementByNameAsync(perma, SmoothScroll); + } + } + + public void ChangePermalink(string? newPermalink, bool doNotNavigate) + { + var perma = DetectPermalink(_navigationManager.Uri); + if (!string.IsNullOrWhiteSpace(perma)) + { + if (newPermalink?.ToLower() == perma?.ToLower()) //same + { + return; + } + + var newUri = _navigationManager.Uri.Replace($"#{perma}", ""); + if (string.IsNullOrWhiteSpace(newPermalink)) //remove + { + _navigationManager.NavigateTo(newUri); + return; + } + + SetBrowserUrl($"{newUri}#{newPermalink}", doNotNavigate); + } + else + { + SetBrowserUrl($"{_navigationManager.Uri}#{newPermalink}", doNotNavigate); + } + } + + private void SetBrowserUrl(string uri, bool doNotNavigate) + { + if(doNotNavigate) + { + //TODO: history.pushState(null, '', url); + return; + } + + _navigationManager.NavigateTo(uri, false); + } + + private string DetectPermalink(string uri) + { + var match = _poundRegex.Match(uri); + if (match.Success && match.Groups.Count == 2) + { + var perma = match.Groups[1].Value; + return perma; } + + return null; } public void Dispose() diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor b/src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor index 14accfb3..9f60da0d 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor +++ b/src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor @@ -17,6 +17,17 @@ _permalinkWatcher.WatchPermaLinks(); } + /// + /// Gets or sets if peramlink should be scrolled smoothly into view or not. + /// Note: smooth scroll on element level might not supported by all Browsers. + /// + [Parameter] + public bool SmoothScroll + { + get => _permalinkWatcher.SmoothScroll; + set { _permalinkWatcher.SmoothScroll = value; } + } + public void Dispose() { _permalinkWatcher?.Dispose(); diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermalinkDetectedEventArgs.cs b/src/Majorsoft.Blazor.Components.PermaLink/PermalinkDetectedEventArgs.cs new file mode 100644 index 00000000..6142708f --- /dev/null +++ b/src/Majorsoft.Blazor.Components.PermaLink/PermalinkDetectedEventArgs.cs @@ -0,0 +1,33 @@ +using System; + +using Microsoft.AspNetCore.Components.Routing; + +namespace Majorsoft.Blazor.Components.PermaLink +{ + /// + /// Event arguments for PermalinkDetected event. + /// + public class PermalinkDetectedEventArgs : EventArgs + { + /// + /// System.EventArgs for Microsoft.AspNetCore.Components.NavigationManager.LocationChanged. + /// + public LocationChangedEventArgs LocationChangedEventArgs { get; } + + /// + /// Detected permalink value. + /// + public string Permalink { get; } = ""; + + /// + /// Default constructor + /// + /// NavigationManager event + /// Detected permalink value + public PermalinkDetectedEventArgs(LocationChangedEventArgs locationChangedEventArgs, string permalink) + { + LocationChangedEventArgs = locationChangedEventArgs; + Permalink = permalink; + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor index 854f979a..1260f076 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor @@ -49,6 +49,7 @@ textarea {

Internal page Permalink examles:

+ Smooth scroll:
  • No permalink
  • Non existing permalink
  • @@ -148,7 +149,9 @@ textarea {
-@code{ +@inject IPermaLinkWatcherService _permalinkWatcher + +@code { private ShowPermaLinkIcon _showIcon = ShowPermaLinkIcon.OnHover; private PermaLinkIconPosition _iconPosition = PermaLinkIconPosition.Right; private PermaLinkStyle _iconStyle; From 7ff462f9271f15ba0889aa3373cd7c44c6a77eb1 Mon Sep 17 00:00:00 2001 From: Major Date: Wed, 8 Sep 2021 18:44:34 +0200 Subject: [PATCH 04/11] Implemented INavigationHistoryService --- .github/docs/JsInterop.md | 27 +++++++- .../JsInteropExtension.cs | 2 + ...oft.Blazor.Components.Common.JsInterop.xml | 60 ++++++++++++++++ .../Navigation/INavigationHistoryService.cs | 61 ++++++++++++++++ .../Navigation/NavigationHistoryService.cs | 38 ++++++++++ .../PermaLinkBlazorServerInitializerTest.cs | 4 ++ .../PermaLinkBlazorServerInitializer.razor | 3 +- .../PermaLinkWatcherService.cs | 14 ++-- .../_Imports.razor | 3 +- .../Components/Index.razor | 1 + .../Components/JSInterop.razor | 3 + .../Components/JsDemo/BrowserHistory.razor | 69 +++++++++++++++++++ .../_Imports.razor | 1 + 13 files changed, 277 insertions(+), 9 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/Navigation/INavigationHistoryService.cs create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/Navigation/NavigationHistoryService.cs create mode 100644 src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserHistory.razor diff --git a/.github/docs/JsInterop.md b/.github/docs/JsInterop.md index 7801a18e..80fc42b9 100644 --- a/.github/docs/JsInterop.md +++ b/.github/docs/JsInterop.md @@ -15,7 +15,6 @@ For code examples [see usage](https://github.com/majorimi/blazor-components/blob You can try it out by using the [demo app](https://blazorextensions.z6.web.core.windows.net/jsinterop). # Features - - **Click JS**: - `ClickBoundariesElement` is a component which wraps the given content to a DIV and subscribes to all click events: `OnOutsideClick`, `OnInsideClick`. - Also an **injectable `IClickBoundariesHandler` service** for callback event handlers. @@ -31,8 +30,9 @@ You can try it out by using the [demo app](https://blazorextensions.z6.web.core. - **Language JS**: is an **injectable `ILanguageService` service** for detect the browser language preference. - **Browser Date JS**: is an **injectable `IBrowserDateService` service** is a simple JS call to `new Date();` to retrieve client machine date and time. - **Browser Theme JS**: is an **injectable `IBrowserThemeService` service** to handle Browser color scheme queries and changes. -- **Geo JS**: is an **injectable `IGeolocationService` service** for detect the device Geolocation (GPS position, speed, heading, etc.). +- **Geo JS**: is an **injectable `IGeolocationService` service** for detect the device [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API) (GPS position, speed, heading, etc.). - **Head JS**: is an **injectable `IHtmlHeadService` service** for accessing and setting HTML document `Head tags`. +- **Browser History JS**: is an **injectable `INavigationHistoryService` service** to access [HTML History API](https://developer.mozilla.org/en-US/docs/Web/API/History) functionality. ## Click JS (See: [demo app](https://blazorextensions.z6.web.core.windows.net/jsinterop#click-js)) **NOTE: Blazor supports `@onclick` event which is equivalent with `OnInsideClick`. @@ -290,7 +290,6 @@ Removes event listener for `prefers-color-scheme` HTML event for the Browser. - **`DisposeAsync`: `ValueTask IAsyncDisposable()` interface**
Implements `IAsyncDisposable` interface the injected service should be Disposed. - ## Geolocation JS (See: [demo app](https://blazorextensions.z6.web.core.windows.net/jsinterop#geo-js)) **Geolocation JS** is an injectable `IGeolocationService` service for **detect the device Geolocation (GPS position, speed, heading, etc.)**. It is using the Geolocation API which allows users to provide their location to web applications if they desire. @@ -328,6 +327,26 @@ If you have multiple fav icon tags set in the Head first call `GetHtmlFavIconsAs - **`DisposeAsync`: `ValueTask IAsyncDisposable()` interface**
Implements `IAsyncDisposable` interface the injected service should be Disposed. +## Browser History JS (See: [demo app](https://blazorextensions.z6.web.core.windows.net/jsinterop#history-js)) +**Browser History JS** is an injectable `INavigationHistoryService` service** to access HTML History API functionality. +It is useful when don't want to rely on Blazor `NavigationManager` which does not have access to full History list and when it navigates trigger a page load/update. + +### Functions +- **`GetLengthAsync`**: **`ValueTask GetLengthAsync()`**
+Returns an Integer representing the number of elements in the session history, including the currently loaded page. +- **`GetScrollRestorationAsync`**: **`ValueTask GetScrollRestorationAsync()`**
+Allows web applications to explicitly set default scroll restoration behavior on history navigation. This property can be either `auto` or `manual`. +- **`BackAsync`**: **`ValueTask BackAsync()`**
+This asynchronous method goes to the previous page in session history, the same action as when the user clicks the browser's Back button. Equivalent to history.go(-1). +- **`ForwardAsync`**: **`ValueTask ForwardAsync()`**
+This asynchronous method goes to the next page in session history, the same action as when the user clicks the browser's Forward button; this is equivalent to history.go(1). +- **`GoAsync`**: **`ValueTask GoAsync(int delta)`**
+Asynchronously loads a page from the session history, identified by its relative location to the current page, for example -1 for the previous page or 1 for the next page. +- **`ReplaceStateAsync`**: **`ValueTask ReplaceStateAsync(ExpandoObject? state, string title, string url)`**
+Updates the most recent entry on the history stack to have the specified data, title, and, if provided, URL. +- **`PushStateAsync`**: **`ValueTask PushStateAsync(ExpandoObject? state, string title, string url)`**
+Pushes the given data onto the session history stack with the specified title (and, if provided, URL). + # Configuration @@ -366,6 +385,8 @@ Add using statement to your Blazor .razor file. Or globally refe @using Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate @*Only if you want to use Browser ColorTheme*@ @using Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme +@*Only if you want to use Browser History*@ +@using Majorsoft.Blazor.Components.Common.JsInterop.History ``` diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs index 3940404e..0fa686e4 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs @@ -9,6 +9,7 @@ using Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents; using Majorsoft.Blazor.Components.Common.JsInterop.Head; using Majorsoft.Blazor.Components.Common.JsInterop.Language; +using Majorsoft.Blazor.Components.Common.JsInterop.Navigation; using Majorsoft.Blazor.Components.Common.JsInterop.Resize; using Majorsoft.Blazor.Components.Common.JsInterop.Scroll; @@ -44,6 +45,7 @@ public static IServiceCollection AddJsInteropExtensions(this IServiceCollection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); return services; } diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml b/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml index 8407ba14..4d236f0f 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Majorsoft.Blazor.Components.Common.JsInterop.xml @@ -561,6 +561,66 @@ Implementation of
+ + + Injectable service to handle Browser history JS Interops. + https://developer.mozilla.org/en-US/docs/Web/API/History + + + + + Returns an Integer representing the number of elements in the session history, including the currently loaded page. For example, for a page loaded in a new tab this property returns 1. + + ValueTask + + + + Allows web applications to explicitly set default scroll restoration behavior on history navigation. This property can be either `auto` or `manual`. + + ValueTask + + + + This asynchronous method goes to the previous page in session history, the same action as when the user clicks the browser's Back button. Equivalent to history.go(-1). + + ValueTask + + + + This asynchronous method goes to the next page in session history, the same action as when the user clicks the browser's Forward button; this is equivalent to history.go(1). + + ValueTask + + + + Asynchronously loads a page from the session history, identified by its relative location to the current page, for example -1 for the previous page or 1 for the next page. + + The position in the history to which you want to move, relative to the current page. A negative value moves backwards, a positive value moves forwards. + ValueTask + + + + Updates the most recent entry on the history stack to have the specified data, title, and, if provided, URL. + + Arbitrary object of the page + Page tile + New URL to show and add to history + ValueTask + + + + Pushes the given data onto the session history stack with the specified title (and, if provided, URL). + + Arbitrary object of the page + Page tile + New URL to show and add to history + ValueTask + + + + Implementation of + + Injectable service to handle JS 'resize' events for HTML element or the whole document. diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Navigation/INavigationHistoryService.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Navigation/INavigationHistoryService.cs new file mode 100644 index 00000000..57102997 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Navigation/INavigationHistoryService.cs @@ -0,0 +1,61 @@ +using System.Dynamic; +using System.Threading.Tasks; + +namespace Majorsoft.Blazor.Components.Common.JsInterop.Navigation +{ + /// + /// Injectable service to handle Browser history JS Interops. + /// https://developer.mozilla.org/en-US/docs/Web/API/History + /// + public interface INavigationHistoryService + { + /// + /// Returns an Integer representing the number of elements in the session history, including the currently loaded page. For example, for a page loaded in a new tab this property returns 1. + /// + /// ValueTask + ValueTask GetLengthAsync(); + + /// + /// Allows web applications to explicitly set default scroll restoration behavior on history navigation. This property can be either `auto` or `manual`. + /// + /// ValueTask + ValueTask GetScrollRestorationAsync(); + + /// + /// This asynchronous method goes to the previous page in session history, the same action as when the user clicks the browser's Back button. Equivalent to history.go(-1). + /// + /// ValueTask + ValueTask BackAsync(); + + /// + /// This asynchronous method goes to the next page in session history, the same action as when the user clicks the browser's Forward button; this is equivalent to history.go(1). + /// + /// ValueTask + ValueTask ForwardAsync(); + + /// + /// Asynchronously loads a page from the session history, identified by its relative location to the current page, for example -1 for the previous page or 1 for the next page. + /// + /// The position in the history to which you want to move, relative to the current page. A negative value moves backwards, a positive value moves forwards. + /// ValueTask + ValueTask GoAsync(int delta); + + /// + /// Updates the most recent entry on the history stack to have the specified data, title, and, if provided, URL. + /// + /// Arbitrary object of the page + /// Page tile + /// New URL to show and add to history + /// ValueTask + ValueTask ReplaceStateAsync(ExpandoObject? state, string title, string url); + + /// + /// Pushes the given data onto the session history stack with the specified title (and, if provided, URL). + /// + /// Arbitrary object of the page + /// Page tile + /// New URL to show and add to history + /// ValueTask + ValueTask PushStateAsync(ExpandoObject? state, string title, string url); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Navigation/NavigationHistoryService.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Navigation/NavigationHistoryService.cs new file mode 100644 index 00000000..43f0ccf6 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Navigation/NavigationHistoryService.cs @@ -0,0 +1,38 @@ +using System.Dynamic; +using System.Threading.Tasks; + +using Microsoft.JSInterop; + +namespace Majorsoft.Blazor.Components.Common.JsInterop.Navigation +{ + /// + /// Implementation of + /// + public class NavigationHistoryService : INavigationHistoryService + { + private readonly IJSRuntime _jSRuntime; + + public NavigationHistoryService(IJSRuntime jSRuntime) + { + _jSRuntime = jSRuntime; + } + + public async ValueTask GetLengthAsync() => await _jSRuntime.InvokeAsync("eval", "history.length"); + public async ValueTask GetScrollRestorationAsync() => await _jSRuntime.InvokeAsync("eval", "history.scrollRestoration"); + + public async ValueTask BackAsync() + => await _jSRuntime.InvokeVoidAsync("history.back"); + + public async ValueTask ForwardAsync() + => await _jSRuntime.InvokeVoidAsync("history.forward"); + + public async ValueTask GoAsync(int delta) + => await _jSRuntime.InvokeVoidAsync("history.go", delta); + + public async ValueTask PushStateAsync(ExpandoObject? state, string title, string url) + => await _jSRuntime.InvokeVoidAsync("history.pushState", state, title, url); + + public async ValueTask ReplaceStateAsync(ExpandoObject? state, string title, string url) + => await _jSRuntime.InvokeVoidAsync("history.replaceState", state, title, url); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkBlazorServerInitializerTest.cs b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkBlazorServerInitializerTest.cs index a0ad3a90..915431d2 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkBlazorServerInitializerTest.cs +++ b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkBlazorServerInitializerTest.cs @@ -2,6 +2,7 @@ using Bunit; +using Majorsoft.Blazor.Components.Common.JsInterop.Navigation; using Majorsoft.Blazor.Components.Common.JsInterop.Scroll; using Majorsoft.Blazor.Components.CommonTestsBase; @@ -18,6 +19,7 @@ public class PermaLinkBlazorServerInitializerTest : ComponentsTestBase _permaLinkWatcherServiceMock; private Mock _scrollHandlerMock; + private Mock _navigationHistoryServiceMock; [TestInitialize] public void Init() @@ -25,10 +27,12 @@ public void Init() var logger = new Mock>(); _permaLinkWatcherServiceMock = new Mock(); _scrollHandlerMock = new Mock(); + _navigationHistoryServiceMock = new Mock(); _testContext.Services.Add(new ServiceDescriptor(typeof(ILogger), logger.Object)); _testContext.Services.Add(new ServiceDescriptor(typeof(IPermaLinkWatcherService), _permaLinkWatcherServiceMock.Object)); _testContext.Services.Add(new ServiceDescriptor(typeof(IScrollHandler), _scrollHandlerMock.Object)); + _testContext.Services.Add(new ServiceDescriptor(typeof(INavigationHistoryService), _navigationHistoryServiceMock.Object)); _testContext.Services.Add(new ServiceDescriptor(typeof(SingletonComponentService), new SingletonComponentService())); _testContext.Services.Add(new ServiceDescriptor(typeof(SingletonComponentService), new SingletonComponentService())); } diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor index d2c0bf84..bb583c82 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor +++ b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor @@ -3,6 +3,7 @@ @inject IScrollHandler _scrollHandler @inject NavigationManager _navigationManager +@inject INavigationHistoryService _navigationHistoryService @inject ILogger _logger @inject SingletonComponentService _singleton @@ -26,7 +27,7 @@ if (firstRender) { //setup permalink - _permalinkWatcher = new PermaLinkWatcherService(_scrollHandler, _navigationManager, _logger); + _permalinkWatcher = new PermaLinkWatcherService(_scrollHandler, _navigationManager, _logger, _navigationHistoryService); _permalinkWatcher.WatchPermaLinks(); } } diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkWatcherService.cs b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkWatcherService.cs index 929cdf7c..bfb67a17 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkWatcherService.cs +++ b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkWatcherService.cs @@ -1,6 +1,7 @@ using System; using System.Text.RegularExpressions; +using Majorsoft.Blazor.Components.Common.JsInterop.Navigation; using Majorsoft.Blazor.Components.Common.JsInterop.Scroll; using Microsoft.AspNetCore.Components; @@ -19,17 +20,22 @@ public class PermaLinkWatcherService : IPermaLinkWatcherService private readonly IScrollHandler _scrollHandler; private readonly NavigationManager _navigationManager; private readonly ILogger _logger; + private readonly INavigationHistoryService _navigationHistoryService; public event EventHandler PermalinkDetected; public bool SmoothScroll { get; set; } - public PermaLinkWatcherService(IScrollHandler scrollHandler, NavigationManager navigationManager, ILogger logger, bool smoothScroll = false) + public PermaLinkWatcherService(IScrollHandler scrollHandler, + NavigationManager navigationManager, + ILogger logger, + INavigationHistoryService navigationHistoryService, + bool smoothScroll = false) { _scrollHandler = scrollHandler; _navigationManager = navigationManager; _logger = logger; - + _navigationHistoryService = navigationHistoryService; SmoothScroll = smoothScroll; } @@ -55,7 +61,7 @@ private void HandleLocationChanged(object sender, LocationChangedEventArgs e) if(PermalinkDetected is not null) { PermalinkDetected.Invoke(this, new PermalinkDetectedEventArgs(e, perma)); - } + } _scrollHandler.ScrollToElementByNameAsync(perma, SmoothScroll); } @@ -90,7 +96,7 @@ private void SetBrowserUrl(string uri, bool doNotNavigate) { if(doNotNavigate) { - //TODO: history.pushState(null, '', url); + _navigationHistoryService?.PushStateAsync(null, "", uri); return; } diff --git a/src/Majorsoft.Blazor.Components.PermaLink/_Imports.razor b/src/Majorsoft.Blazor.Components.PermaLink/_Imports.razor index b3acd5c8..400301eb 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.PermaLink/_Imports.razor @@ -5,4 +5,5 @@ @using Majorsoft.Blazor.Components.Common.JsInterop.Scroll @using Majorsoft.Blazor.Components.Common.JsInterop.Clipboard -@using Majorsoft.Blazor.Components.Core.Events \ No newline at end of file +@using Majorsoft.Blazor.Components.Core.Events +@using Majorsoft.Blazor.Components.Common.JsInterop.Navigation \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor index 480e6cd9..ff6530e2 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor @@ -50,6 +50,7 @@
  • Browser Theme Js
  • Geo Js
  • Head Js
  • +
  • Browser History Js
  • diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor index ddabac18..40a64d26 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor @@ -35,6 +35,7 @@ Smooth scroll:
  • Browser Theme Js
  • Geolocation Js
  • Head Js
  • +
  • Browser History Js
  • @@ -62,6 +63,8 @@ Smooth scroll: + +
    @*Scroll to page end with ScrollHandler for custom floating component see ScrollToPageTop*@ diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserHistory.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserHistory.razor new file mode 100644 index 00000000..03d40ecf --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserHistory.razor @@ -0,0 +1,69 @@ +
    + +

    Browser History JS

    +
    + +

    + Browser History JS is an injectable INavigationHistoryService to access + HTML History API functionality. + It is useful when don't want to rely on Blazor NavigationManager which does not have access to full History list and when it navigates trigger a page load/update. +

    + +
    +
    +

    + Number of History items: @_historyCount +
    + Scroll restoration behavior: @_scrollRestore +

    +
    +
    + +
    +
    +
    History manipulation:
    + + History Title: + History URL: + + + +
    +
    + +
    +
    +
    Navigations:
    + Go with Delta: @_goDelta + + + + +
    +
    +
    + +@inject INavigationHistoryService _navigationHistoryService +@inject NavigationManager _navigationManager + +@code { + private int _historyCount = 0; + private string _scrollRestore = ""; + private int _goDelta = 0; + private string _title = ""; + private string _url = ""; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if(firstRender) + { + _historyCount = await _navigationHistoryService.GetLengthAsync(); + _scrollRestore = await _navigationHistoryService.GetScrollRestorationAsync(); + + _url = _navigationManager.Uri; + + StateHasChanged(); + } + } + +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor index 0f127b7d..aa435835 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor @@ -40,6 +40,7 @@ @using Majorsoft.Blazor.Components.Common.JsInterop.Head @using Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate @using Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme +@using Majorsoft.Blazor.Components.Common.JsInterop.Navigation @using Majorsoft.Blazor.Components.PermaLink From 0325ae6d43fd9a9a81735e45f34fc79a990a822d Mon Sep 17 00:00:00 2001 From: Major Date: Fri, 10 Sep 2021 14:19:49 +0200 Subject: [PATCH 05/11] Implemented TabActivation with permalinks --- .github/docs/PermaLink.md | 2 + .github/docs/Tabs.md | 45 ++++++- ....Blazor.Components.Common.JsInterop.csproj | 2 +- .../IPermaLinkWatcherService.cs | 7 ++ .../Majorsoft.Blazor.Components.PermaLink.xml | 7 ++ .../PermaLinkWatcherService.cs | 33 +++-- .../TabsPanelTest.cs | 117 ++++++++++++++++++ .../Majorsoft.Blazor.Components.Tabs.csproj | 1 + .../Majorsoft.Blazor.Components.Tabs.xml | 12 ++ .../TabItem.razor | 6 + .../TabsPanel.razor | 70 +++++++++-- .../_Imports.razor | 4 +- .../Components/Tabs.razor | 12 +- src/Majorsoft.Blazor.Components.sln | 2 +- 14 files changed, 291 insertions(+), 29 deletions(-) diff --git a/.github/docs/PermaLink.md b/.github/docs/PermaLink.md index 81ef2520..045bd811 100644 --- a/.github/docs/PermaLink.md +++ b/.github/docs/PermaLink.md @@ -34,6 +34,8 @@ For more details see Usage section. Starts a navigation watcher which will check for Permalinks in the URLs. - **`ChangePermalink()`**: **`void ChangePermalink(string? newPermalink, bool doNotNavigate)`**
    Modify the current URL with given new peralink value and trigger navigation or just update browser History. +- **`CheckPermalink()`**: **`string? CheckPermalink(bool triggerEvent = false)`**
    +Checks the current URL for permalink again and re-triggers `PermalinkDetected` event if requested. - **`Dispose()`: `@implements IDisposable` interface**
    Component implements `IDisposable` interface Blazor framework will call it when parent removed from render tree. diff --git a/.github/docs/Tabs.md b/.github/docs/Tabs.md index 74ae8d83..45b21969 100644 --- a/.github/docs/Tabs.md +++ b/.github/docs/Tabs.md @@ -37,6 +37,9 @@ Sets all `TabItem` elements height in `px`. Sets all `TabItem` elements element width in `px`. - **`Disabled`: `bool { get; set; }` (default: false)**
    Determines whether all the rendered HTML elements should be disabled or not. +- **`AllowTabActivationByPermalink`: `bool { get; set; }` (default: true)**
    +Enables or disables `TabItem` activation with URL Permalink fragment. +**NOTE: in order to make TabActivation work `Majorsoft.Blazor.Components.PermaLink` component is used and it MUST [set up correctly](https://github.com/majorimi/blazor-components/blob/master/.github/docs/PermaLink.md#configuration)!** - **`Animate`: `bool { get; set; }` (default: true)**
    Determines to apply CSS animation and transion on Tab changes or not. - **`TabPositon`: `TabPositons { get; set; }` (default: TabPositons.Left)**
    @@ -67,6 +70,9 @@ Required HTML content to show content of current TabItem. Determines whether the current rendered TabItem should be disabled or not. - **`Hidden`: `bool { get; set; }` (default: false)**
    Determines whether the current rendered TabItem should be hidden or not. +- **`Permalink`: `string { get; set; }` (default: "")**
    +Permalink value to append to the URL and activate the `TabItem` based on matching value. +**NOTE: in order to make TabActivation work `Majorsoft.Blazor.Components.PermaLink` component is used and it MUST [set up correctly](https://github.com/majorimi/blazor-components/blob/master/.github/docs/PermaLink.md#configuration)!** **Arbitrary HTML attributes e.g.: `tabindex="1"` will be passed to the corresponding rendered HTML element ``**. @@ -88,11 +94,42 @@ Add using statement to your Blazor `.razor` file. Or globally re @using Majorsoft.Blazor.Components.Tabs ``` +### Dependences +**Majorsoft.Blazor.Components.Tabs** package "partially" depends on other Majorsoft Nuget packages: +- [Majorsoft.Blazor.Components.Common.JsInterop](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Common.JsInterop) +which handles JS Interop for many features e.g. scrolling, etc. +- [Majorsoft.Blazor.Components.Common.PermaLink](https://www.nuget.org/packages/Majorsoft.Blazor.Components.PermaLink) +which track navigations (URL changes) and identify permalink elements. + +**NOTE: only TabItem activation feature depend on Permalink. If you don't want to use that feature just leave `Permalink` parameters empty and do not setup PermalinkWatcher. +Also later this feature can be disabled by `AllowTabActivationByPermalink = false`.** + ### `TabsPanel` and `TabItem` usage Following code example shows how to use **`TabsPanel`** with **`TabItem`** component in your Blazor App. +**NOTE: to use TabActivation feature `Permalink="Tab1"` must be set and Permalink services must be [configured correctly](https://github.com/majorimi/blazor-components/blob/master/.github/docs/PermaLink.md#configuration)!** + ``` +@*Simple tab usage*@ + + + +
    Tab1
    + Tab1 +
    + +
    Tab2
    + Tab2 +
    + +
    Tab3
    + Tab3 +
    +
    +
    + +@*Advanced tab usage*@ - + - + -