From 09013c9289d4c86dc144c2d04be78d1542965dfd Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Fri, 19 Mar 2021 21:31:54 +0100 Subject: [PATCH 01/69] Cleanup after all maps instance disposed. --- .../Google/GoogleMap.razor | 5 +++++ .../wwwroot/googleMaps.js | 13 ++++++++++++- .../wwwroot/googleMaps.min.js | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Maps/Google/GoogleMap.razor b/src/Majorsoft.Blazor.Components.Maps/Google/GoogleMap.razor index 0ab65899..153d756e 100644 --- a/src/Majorsoft.Blazor.Components.Maps/Google/GoogleMap.razor +++ b/src/Majorsoft.Blazor.Components.Maps/Google/GoogleMap.razor @@ -1089,6 +1089,11 @@ { await _geolocationService.DisposeAsync(); } + + if(_mapService is not null) + { + await _mapService.DisposeAsync(); + } if (_markers is not null) { diff --git a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js index 76338773..fe7bb981 100644 --- a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js +++ b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js @@ -572,6 +572,17 @@ export function dispose(elementId) { mapWithDotnetRef.map = null; mapWithDotnetRef.ref = null; - removeElementIdWithDotnetRef(elementId); + removeElementIdWithDotnetRef(_mapsElementDict, elementId); + + if (_mapsElementDict.length === 0) { //Last item cleanup headers. + let scriptTags = document.querySelectorAll('head > script'); + scriptTags.forEach(scriptTag => { + if (scriptTag.getAttribute('src').startsWith("https://maps.googleapis.com/maps")) { + document.head.removeChild(scriptTag); + } + }); + + google = null; + } } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js index dbd8bc4e..385cf52c 100644 --- a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js +++ b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js @@ -1 +1 @@ -export function init(t,i,r,f,e){if(t&&i&&r){u(n,i,r,f,e);let s=!1,c=document.querySelectorAll("head > script");if(c.forEach(n=>{if(n.getAttribute("src").startsWith("https://maps.googleapis.com/maps/api/js?key=")){s=!0;return}}),!s){let h=document.createElement("script");h.src="https://polyfill.io/v3/polyfill.min.js?features=default";document.head.appendChild(h);let l="https://maps.googleapis.com/maps/api/js?key="+t+"&callback=initGoogleMaps&libraries=&v=weekly",o=document.createElement("script");o.src=l;o.defer=!0;document.head.appendChild(o)}}}function u(n,t,i,r,u){for(let i=0;i<n.length;i++)if(n[i].key===t)return;!1||n.push({key:t,value:{ref:i,map:null,bgColor:r,ctrSize:u}})}function f(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t){n.splice(i,1);break}}function t(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t)return n[i].value}export function setCenterCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.setCenter({lat:r,lng:u})}}export function setCenterAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.setCenter(n[0].geometry.location)})}}export function panToCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.panTo({lat:r,lng:u})}}export function panToAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.panTo(n[0].geometry.location)})}}export function setZoom(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setZoom(r)}}export function setMapType(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setMapTypeId(r)}}export function setHeading(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setHeading(r)}}export function setTilt(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setTilt(r)}}export function setClickableIcons(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setClickableIcons(r)}}export function setOptions(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setOptions(r)}}export function resizeMap(i){if(i){let r=t(n,i);r&&r.map&&google.maps.event.trigger(r.map,"resize")}}export function createCustomControls(i,r){if(i&&r){let f=t(n,i);if(f&&f.map)for(var u=0;u<r.length;u++){let n=r[u],t=document.createElement("div");t.innerHTML=n.content;f.map.controls[n.controlPosition].push(t);let i=n.id,e=f.ref;t.addEventListener("click",()=>{e.invokeMethodAsync("CustomControlClicked",i)})}}}export function createMarkers(r,u){if(r&&u&&u.length){let o=t(n,r);if(o&&o.map)for(var f=0;f<u.length;f++){let n=u[f],t=new google.maps.Marker({id:n.id,crossOnDrag:n.crossOnDrag,optimized:n.optimized});if(t.setMap(o.map),e(n,t),i.push(t),n.clickable){let i=null;n.infoWindow&&(i=new google.maps.InfoWindow({content:n.infoWindow.content,maxWidth:n.infoWindow.maxWidth}));t.addListener("click",()=>{o.ref.invokeMethodAsync("MarkerClicked",n.id),i&&i.open(o.map,t)})}if(n.draggable){t.addListener("drag",()=>{i("MarkerDrag",n.id,t.getPosition().toJSON())});t.addListener("dragend",()=>{i("MarkerDragEnd",n.id,t.getPosition().toJSON())});t.addListener("dragstart",()=>{i("MarkerDragStart",n.id,t.getPosition().toJSON())});function i(n,t,i){let r={Latitude:i.lat,Longitude:i.lng};o.ref.invokeMethodAsync(n,t,r)}}}}}export function removeMarkers(r,u){if(r&&u&&u.length){let e=t(n,r);if(e&&e.map)for(var f=0;f<u.length;f++){let n=u[f];i.forEach((t,r)=>{if(n.id==t.id){t.setMap(null);i.splice(r,1);return}})}}}function e(n,t){t&&n&&(t.setPosition({lat:n.position.latitude,lng:n.position.longitude}),t.anchorPoint=n.anchorPoint?{x:n.anchorPoint.x,y:n.anchorPoint.y}:null,t.setAnimation(n.animation),t.setClickable(n.clickable),t.crossOnDrag=n.crossOnDrag,t.setCursor(n.cursor),t.setDraggable(n.draggable),t.setIcon(n.icon),t.setLabel(n.label),t.setOpacity(n.opacity),t.optimized=n.optimized,t.setShape(n.shape),t.setTitle(n.title),t.setVisible(n.visible),t.setZIndex(n.zIndex))}export function getAddressCoordinates(i,u){r(u,function(r){if(r){let u=t(n,i);u&&u.map&&u.ref.invokeMethodAsync("AddressSearch",r)}})}function r(n,t){let i=new google.maps.Geocoder;i.geocode({address:n},function(n,i){i==google.maps.GeocoderStatus.OK&&t(n)})}export function dispose(i){if(i){let r=t(n,i);r.map=null;r.ref=null;f(i)}}window.initGoogleMaps=()=>{for(let i=0;i<n.length;i++){let f=n[i].key,e=n[i].value,r=new google.maps.Map(document.getElementById(f),{backgroundColor:e.bgColor,controlSize:e.ctrSize});r.elementId=f;n[i].value.map=r;function u(i,u){if(r&&r.elementId&&i){let f=t(n,r.elementId);if(f){let n=i.latLng.toJSON(),t={Latitude:n.lat,Longitude:n.lng};f.ref.invokeMethodAsync(u,t)}}}r.addListener("click",n=>{u(n,"MapClicked")});r.addListener("dblclick",n=>{u(n,"MapDoubleClicked")});r.addListener("contextmenu",n=>{u(n,"MapContextMenu")});r.addListener("mouseup",n=>{u(n,"MapMouseUp")});r.addListener("mousedown",n=>{u(n,"MapMouseDown")});r.addListener("mousemove",n=>{u(n,"MapMouseMove")});r.addListener("mouseover",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOver")}});r.addListener("mouseout",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOut")}});r.addListener("center_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapCenterChanged",t)}}});r.addListener("zoom_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapZoomChanged",r.getZoom())}});r.addListener("maptypeid_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTypeIdChanged",r.getMapTypeId())}});r.addListener("heading_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapHeadingChanged",r.getHeading())}});r.addListener("tilt_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTiltChanged",r.getTilt())}});r.addListener("bounds_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapBoundsChanged")}});r.addListener("projection_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapProjectionChanged")}});r.addListener("draggable_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapDraggableChanged")}});r.addListener("streetview_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapStreetviewChanged")}});r.addListener("drag",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDrag",t)}}});r.addListener("dragend",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragEnd",t)}}});r.addListener("dragstart",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragStart",t)}}});r.addListener("resize",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i){let n={Width:r.getDiv().offsetWidth,Height:r.getDiv().offsetHeight};i.ref.invokeMethodAsync("MapResized",n)}}});r.addListener("tilesloaded",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTilesLoaded")}});r.addListener("idle",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapIdle")}});n[i].value.ref.invokeMethodAsync("MapInitialized",f)}};let n=[],i=[]; \ No newline at end of file +export function init(t,i,r,f,e){if(t&&i&&r){u(n,i,r,f,e);let s=!1,c=document.querySelectorAll("head > script");if(c.forEach(n=>{if(n.getAttribute("src").startsWith("https://maps.googleapis.com/maps/api/js?key=")){s=!0;return}}),s){window.initGoogleMaps();return}let h=document.createElement("script");h.src="https://polyfill.io/v3/polyfill.min.js?features=default";document.head.appendChild(h);let l="https://maps.googleapis.com/maps/api/js?key="+t+"&callback=initGoogleMaps&libraries=&v=weekly",o=document.createElement("script");o.src=l;o.defer=!0;document.head.appendChild(o)}}function u(n,t,i,r,u){for(let i=0;i<n.length;i++)if(n[i].key===t)return;!1||n.push({key:t,value:{ref:i,map:null,bgColor:r,ctrSize:u}})}function f(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t){n.splice(i,1);break}}function t(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t)return n[i].value}export function setCenterCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.setCenter({lat:r,lng:u})}}export function setCenterAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.setCenter(n[0].geometry.location)})}}export function panToCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.panTo({lat:r,lng:u})}}export function panToAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.panTo(n[0].geometry.location)})}}export function setZoom(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setZoom(r)}}export function setMapType(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setMapTypeId(r)}}export function setHeading(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setHeading(r)}}export function setTilt(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setTilt(r)}}export function setClickableIcons(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setClickableIcons(r)}}export function setOptions(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setOptions(r)}}export function resizeMap(i){if(i){let r=t(n,i);r&&r.map&&google.maps.event.trigger(r.map,"resize")}}export function createCustomControls(i,r){if(i&&r){let f=t(n,i);if(f&&f.map)for(var u=0;u<r.length;u++){let n=r[u],t=document.createElement("div");t.innerHTML=n.content;f.map.controls[n.controlPosition].push(t);let i=n.id,e=f.ref;t.addEventListener("click",()=>{e.invokeMethodAsync("CustomControlClicked",i)})}}}export function createMarkers(r,u){if(r&&u&&u.length){let o=t(n,r);if(o&&o.map)for(var f=0;f<u.length;f++){let n=u[f],t=new google.maps.Marker({id:n.id,crossOnDrag:n.crossOnDrag,optimized:n.optimized});if(t.setMap(o.map),e(n,t),i.push(t),n.clickable){let i=null;n.infoWindow&&(i=new google.maps.InfoWindow({content:n.infoWindow.content,maxWidth:n.infoWindow.maxWidth}));t.addListener("click",()=>{o.ref.invokeMethodAsync("MarkerClicked",n.id),i&&i.open(o.map,t)})}if(n.draggable){t.addListener("drag",()=>{i("MarkerDrag",n.id,t.getPosition().toJSON())});t.addListener("dragend",()=>{i("MarkerDragEnd",n.id,t.getPosition().toJSON())});t.addListener("dragstart",()=>{i("MarkerDragStart",n.id,t.getPosition().toJSON())});function i(n,t,i){let r={Latitude:i.lat,Longitude:i.lng};o.ref.invokeMethodAsync(n,t,r)}}}}}export function removeMarkers(r,u){if(r&&u&&u.length){let e=t(n,r);if(e&&e.map)for(var f=0;f<u.length;f++){let n=u[f];i.forEach((t,r)=>{if(n.id==t.id){t.setMap(null);i.splice(r,1);return}})}}}function e(n,t){t&&n&&(t.setPosition({lat:n.position.latitude,lng:n.position.longitude}),t.anchorPoint=n.anchorPoint?{x:n.anchorPoint.x,y:n.anchorPoint.y}:null,t.setAnimation(n.animation),t.setClickable(n.clickable),t.crossOnDrag=n.crossOnDrag,t.setCursor(n.cursor),t.setDraggable(n.draggable),t.setIcon(n.icon),t.setLabel(n.label),t.setOpacity(n.opacity),t.optimized=n.optimized,t.setShape(n.shape),t.setTitle(n.title),t.setVisible(n.visible),t.setZIndex(n.zIndex))}export function getAddressCoordinates(i,u){r(u,function(r){if(r){let u=t(n,i);u&&u.map&&u.ref.invokeMethodAsync("AddressSearch",r)}})}function r(n,t){let i=new google.maps.Geocoder;i.geocode({address:n},function(n,i){i==google.maps.GeocoderStatus.OK&&t(n)})}export function dispose(i){if(i){let r=t(n,i);r.map=null;r.ref=null;f(n,i)}}window.initGoogleMaps=()=>{for(let i=0;i<n.length;i++){let f=n[i].key,e=n[i].value,r=new google.maps.Map(document.getElementById(f),{backgroundColor:e.bgColor,controlSize:e.ctrSize});r.elementId=f;n[i].value.map=r;function u(i,u){if(r&&r.elementId&&i){let f=t(n,r.elementId);if(f){let n=i.latLng.toJSON(),t={Latitude:n.lat,Longitude:n.lng};f.ref.invokeMethodAsync(u,t)}}}r.addListener("click",n=>{u(n,"MapClicked")});r.addListener("dblclick",n=>{u(n,"MapDoubleClicked")});r.addListener("contextmenu",n=>{u(n,"MapContextMenu")});r.addListener("mouseup",n=>{u(n,"MapMouseUp")});r.addListener("mousedown",n=>{u(n,"MapMouseDown")});r.addListener("mousemove",n=>{u(n,"MapMouseMove")});r.addListener("mouseover",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOver")}});r.addListener("mouseout",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOut")}});r.addListener("center_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapCenterChanged",t)}}});r.addListener("zoom_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapZoomChanged",r.getZoom())}});r.addListener("maptypeid_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTypeIdChanged",r.getMapTypeId())}});r.addListener("heading_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapHeadingChanged",r.getHeading())}});r.addListener("tilt_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTiltChanged",r.getTilt())}});r.addListener("bounds_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapBoundsChanged")}});r.addListener("projection_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapProjectionChanged")}});r.addListener("draggable_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapDraggableChanged")}});r.addListener("streetview_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapStreetviewChanged")}});r.addListener("drag",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDrag",t)}}});r.addListener("dragend",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragEnd",t)}}});r.addListener("dragstart",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragStart",t)}}});r.addListener("resize",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i){let n={Width:r.getDiv().offsetWidth,Height:r.getDiv().offsetHeight};i.ref.invokeMethodAsync("MapResized",n)}}});r.addListener("tilesloaded",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTilesLoaded")}});r.addListener("idle",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapIdle")}});n[i].value.ref.invokeMethodAsync("MapInitialized",f)}};let n=[],i=[]; \ No newline at end of file From aa3414314ec017e0f9aaa8d01ffa85eba0a5ee6c Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Fri, 19 Mar 2021 21:49:42 +0100 Subject: [PATCH 02/69] Fixed issue with navigated back to page which already has Google map initialized. --- .../wwwroot/googleMaps.js | 18 +++++++----------- .../wwwroot/googleMaps.min.js | 2 +- .../Components/MapsGoogle.razor | 5 ++++- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js index fe7bb981..bdb034c9 100644 --- a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js +++ b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js @@ -15,6 +15,9 @@ }); if (scriptsIncluded) { //Prevent adding JS scripts to page multiple times. + if (window.google) { + window.initGoogleMaps(); //Page was navigated + } return; } @@ -37,6 +40,10 @@ window.initGoogleMaps = () => { let elementId = _mapsElementDict[i].key; let mapInfo = _mapsElementDict[i].value; + if (_mapsElementDict[i].value.map) { //Map already created + continue; + } + //Create Map let map = new google.maps.Map(document.getElementById(elementId), { backgroundColor: mapInfo.bgColor, @@ -573,16 +580,5 @@ export function dispose(elementId) { mapWithDotnetRef.ref = null; removeElementIdWithDotnetRef(_mapsElementDict, elementId); - - if (_mapsElementDict.length === 0) { //Last item cleanup headers. - let scriptTags = document.querySelectorAll('head > script'); - scriptTags.forEach(scriptTag => { - if (scriptTag.getAttribute('src').startsWith("https://maps.googleapis.com/maps")) { - document.head.removeChild(scriptTag); - } - }); - - google = null; - } } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js index 385cf52c..26b1cf06 100644 --- a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js +++ b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js @@ -1 +1 @@ -export function init(t,i,r,f,e){if(t&&i&&r){u(n,i,r,f,e);let s=!1,c=document.querySelectorAll("head > script");if(c.forEach(n=>{if(n.getAttribute("src").startsWith("https://maps.googleapis.com/maps/api/js?key=")){s=!0;return}}),s){window.initGoogleMaps();return}let h=document.createElement("script");h.src="https://polyfill.io/v3/polyfill.min.js?features=default";document.head.appendChild(h);let l="https://maps.googleapis.com/maps/api/js?key="+t+"&callback=initGoogleMaps&libraries=&v=weekly",o=document.createElement("script");o.src=l;o.defer=!0;document.head.appendChild(o)}}function u(n,t,i,r,u){for(let i=0;i<n.length;i++)if(n[i].key===t)return;!1||n.push({key:t,value:{ref:i,map:null,bgColor:r,ctrSize:u}})}function f(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t){n.splice(i,1);break}}function t(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t)return n[i].value}export function setCenterCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.setCenter({lat:r,lng:u})}}export function setCenterAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.setCenter(n[0].geometry.location)})}}export function panToCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.panTo({lat:r,lng:u})}}export function panToAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.panTo(n[0].geometry.location)})}}export function setZoom(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setZoom(r)}}export function setMapType(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setMapTypeId(r)}}export function setHeading(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setHeading(r)}}export function setTilt(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setTilt(r)}}export function setClickableIcons(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setClickableIcons(r)}}export function setOptions(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setOptions(r)}}export function resizeMap(i){if(i){let r=t(n,i);r&&r.map&&google.maps.event.trigger(r.map,"resize")}}export function createCustomControls(i,r){if(i&&r){let f=t(n,i);if(f&&f.map)for(var u=0;u<r.length;u++){let n=r[u],t=document.createElement("div");t.innerHTML=n.content;f.map.controls[n.controlPosition].push(t);let i=n.id,e=f.ref;t.addEventListener("click",()=>{e.invokeMethodAsync("CustomControlClicked",i)})}}}export function createMarkers(r,u){if(r&&u&&u.length){let o=t(n,r);if(o&&o.map)for(var f=0;f<u.length;f++){let n=u[f],t=new google.maps.Marker({id:n.id,crossOnDrag:n.crossOnDrag,optimized:n.optimized});if(t.setMap(o.map),e(n,t),i.push(t),n.clickable){let i=null;n.infoWindow&&(i=new google.maps.InfoWindow({content:n.infoWindow.content,maxWidth:n.infoWindow.maxWidth}));t.addListener("click",()=>{o.ref.invokeMethodAsync("MarkerClicked",n.id),i&&i.open(o.map,t)})}if(n.draggable){t.addListener("drag",()=>{i("MarkerDrag",n.id,t.getPosition().toJSON())});t.addListener("dragend",()=>{i("MarkerDragEnd",n.id,t.getPosition().toJSON())});t.addListener("dragstart",()=>{i("MarkerDragStart",n.id,t.getPosition().toJSON())});function i(n,t,i){let r={Latitude:i.lat,Longitude:i.lng};o.ref.invokeMethodAsync(n,t,r)}}}}}export function removeMarkers(r,u){if(r&&u&&u.length){let e=t(n,r);if(e&&e.map)for(var f=0;f<u.length;f++){let n=u[f];i.forEach((t,r)=>{if(n.id==t.id){t.setMap(null);i.splice(r,1);return}})}}}function e(n,t){t&&n&&(t.setPosition({lat:n.position.latitude,lng:n.position.longitude}),t.anchorPoint=n.anchorPoint?{x:n.anchorPoint.x,y:n.anchorPoint.y}:null,t.setAnimation(n.animation),t.setClickable(n.clickable),t.crossOnDrag=n.crossOnDrag,t.setCursor(n.cursor),t.setDraggable(n.draggable),t.setIcon(n.icon),t.setLabel(n.label),t.setOpacity(n.opacity),t.optimized=n.optimized,t.setShape(n.shape),t.setTitle(n.title),t.setVisible(n.visible),t.setZIndex(n.zIndex))}export function getAddressCoordinates(i,u){r(u,function(r){if(r){let u=t(n,i);u&&u.map&&u.ref.invokeMethodAsync("AddressSearch",r)}})}function r(n,t){let i=new google.maps.Geocoder;i.geocode({address:n},function(n,i){i==google.maps.GeocoderStatus.OK&&t(n)})}export function dispose(i){if(i){let r=t(n,i);r.map=null;r.ref=null;f(n,i)}}window.initGoogleMaps=()=>{for(let i=0;i<n.length;i++){let f=n[i].key,e=n[i].value,r=new google.maps.Map(document.getElementById(f),{backgroundColor:e.bgColor,controlSize:e.ctrSize});r.elementId=f;n[i].value.map=r;function u(i,u){if(r&&r.elementId&&i){let f=t(n,r.elementId);if(f){let n=i.latLng.toJSON(),t={Latitude:n.lat,Longitude:n.lng};f.ref.invokeMethodAsync(u,t)}}}r.addListener("click",n=>{u(n,"MapClicked")});r.addListener("dblclick",n=>{u(n,"MapDoubleClicked")});r.addListener("contextmenu",n=>{u(n,"MapContextMenu")});r.addListener("mouseup",n=>{u(n,"MapMouseUp")});r.addListener("mousedown",n=>{u(n,"MapMouseDown")});r.addListener("mousemove",n=>{u(n,"MapMouseMove")});r.addListener("mouseover",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOver")}});r.addListener("mouseout",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOut")}});r.addListener("center_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapCenterChanged",t)}}});r.addListener("zoom_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapZoomChanged",r.getZoom())}});r.addListener("maptypeid_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTypeIdChanged",r.getMapTypeId())}});r.addListener("heading_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapHeadingChanged",r.getHeading())}});r.addListener("tilt_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTiltChanged",r.getTilt())}});r.addListener("bounds_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapBoundsChanged")}});r.addListener("projection_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapProjectionChanged")}});r.addListener("draggable_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapDraggableChanged")}});r.addListener("streetview_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapStreetviewChanged")}});r.addListener("drag",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDrag",t)}}});r.addListener("dragend",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragEnd",t)}}});r.addListener("dragstart",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragStart",t)}}});r.addListener("resize",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i){let n={Width:r.getDiv().offsetWidth,Height:r.getDiv().offsetHeight};i.ref.invokeMethodAsync("MapResized",n)}}});r.addListener("tilesloaded",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTilesLoaded")}});r.addListener("idle",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapIdle")}});n[i].value.ref.invokeMethodAsync("MapInitialized",f)}};let n=[],i=[]; \ No newline at end of file +export function init(t,i,r,f,e){if(t&&i&&r){u(n,i,r,f,e);let s=!1,c=document.querySelectorAll("head > script");if(c.forEach(n=>{if(n.getAttribute("src").startsWith("https://maps.googleapis.com/maps/api/js?key=")){s=!0;return}}),s){window.google&&window.initGoogleMaps();return}let h=document.createElement("script");h.src="https://polyfill.io/v3/polyfill.min.js?features=default";document.head.appendChild(h);let l="https://maps.googleapis.com/maps/api/js?key="+t+"&callback=initGoogleMaps&libraries=&v=weekly",o=document.createElement("script");o.src=l;o.defer=!0;document.head.appendChild(o)}}function u(n,t,i,r,u){for(let i=0;i<n.length;i++)if(n[i].key===t)return;!1||n.push({key:t,value:{ref:i,map:null,bgColor:r,ctrSize:u}})}function f(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t){n.splice(i,1);break}}function t(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t)return n[i].value}export function setCenterCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.setCenter({lat:r,lng:u})}}export function setCenterAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.setCenter(n[0].geometry.location)})}}export function panToCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.panTo({lat:r,lng:u})}}export function panToAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.panTo(n[0].geometry.location)})}}export function setZoom(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setZoom(r)}}export function setMapType(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setMapTypeId(r)}}export function setHeading(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setHeading(r)}}export function setTilt(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setTilt(r)}}export function setClickableIcons(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setClickableIcons(r)}}export function setOptions(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setOptions(r)}}export function resizeMap(i){if(i){let r=t(n,i);r&&r.map&&google.maps.event.trigger(r.map,"resize")}}export function createCustomControls(i,r){if(i&&r){let f=t(n,i);if(f&&f.map)for(var u=0;u<r.length;u++){let n=r[u],t=document.createElement("div");t.innerHTML=n.content;f.map.controls[n.controlPosition].push(t);let i=n.id,e=f.ref;t.addEventListener("click",()=>{e.invokeMethodAsync("CustomControlClicked",i)})}}}export function createMarkers(r,u){if(r&&u&&u.length){let o=t(n,r);if(o&&o.map)for(var f=0;f<u.length;f++){let n=u[f],t=new google.maps.Marker({id:n.id,crossOnDrag:n.crossOnDrag,optimized:n.optimized});if(t.setMap(o.map),e(n,t),i.push(t),n.clickable){let i=null;n.infoWindow&&(i=new google.maps.InfoWindow({content:n.infoWindow.content,maxWidth:n.infoWindow.maxWidth}));t.addListener("click",()=>{o.ref.invokeMethodAsync("MarkerClicked",n.id),i&&i.open(o.map,t)})}if(n.draggable){t.addListener("drag",()=>{i("MarkerDrag",n.id,t.getPosition().toJSON())});t.addListener("dragend",()=>{i("MarkerDragEnd",n.id,t.getPosition().toJSON())});t.addListener("dragstart",()=>{i("MarkerDragStart",n.id,t.getPosition().toJSON())});function i(n,t,i){let r={Latitude:i.lat,Longitude:i.lng};o.ref.invokeMethodAsync(n,t,r)}}}}}export function removeMarkers(r,u){if(r&&u&&u.length){let e=t(n,r);if(e&&e.map)for(var f=0;f<u.length;f++){let n=u[f];i.forEach((t,r)=>{if(n.id==t.id){t.setMap(null);i.splice(r,1);return}})}}}function e(n,t){t&&n&&(t.setPosition({lat:n.position.latitude,lng:n.position.longitude}),t.anchorPoint=n.anchorPoint?{x:n.anchorPoint.x,y:n.anchorPoint.y}:null,t.setAnimation(n.animation),t.setClickable(n.clickable),t.crossOnDrag=n.crossOnDrag,t.setCursor(n.cursor),t.setDraggable(n.draggable),t.setIcon(n.icon),t.setLabel(n.label),t.setOpacity(n.opacity),t.optimized=n.optimized,t.setShape(n.shape),t.setTitle(n.title),t.setVisible(n.visible),t.setZIndex(n.zIndex))}export function getAddressCoordinates(i,u){r(u,function(r){if(r){let u=t(n,i);u&&u.map&&u.ref.invokeMethodAsync("AddressSearch",r)}})}function r(n,t){let i=new google.maps.Geocoder;i.geocode({address:n},function(n,i){i==google.maps.GeocoderStatus.OK&&t(n)})}export function dispose(i){if(i){let r=t(n,i);r.map=null;r.ref=null;f(n,i)}}window.initGoogleMaps=()=>{for(let i=0;i<n.length;i++){let f=n[i].key,e=n[i].value;if(!n[i].value.map){let r=new google.maps.Map(document.getElementById(f),{backgroundColor:e.bgColor,controlSize:e.ctrSize});r.elementId=f;n[i].value.map=r;function u(i,u){if(r&&r.elementId&&i){let f=t(n,r.elementId);if(f){let n=i.latLng.toJSON(),t={Latitude:n.lat,Longitude:n.lng};f.ref.invokeMethodAsync(u,t)}}}r.addListener("click",n=>{u(n,"MapClicked")});r.addListener("dblclick",n=>{u(n,"MapDoubleClicked")});r.addListener("contextmenu",n=>{u(n,"MapContextMenu")});r.addListener("mouseup",n=>{u(n,"MapMouseUp")});r.addListener("mousedown",n=>{u(n,"MapMouseDown")});r.addListener("mousemove",n=>{u(n,"MapMouseMove")});r.addListener("mouseover",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOver")}});r.addListener("mouseout",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOut")}});r.addListener("center_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapCenterChanged",t)}}});r.addListener("zoom_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapZoomChanged",r.getZoom())}});r.addListener("maptypeid_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTypeIdChanged",r.getMapTypeId())}});r.addListener("heading_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapHeadingChanged",r.getHeading())}});r.addListener("tilt_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTiltChanged",r.getTilt())}});r.addListener("bounds_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapBoundsChanged")}});r.addListener("projection_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapProjectionChanged")}});r.addListener("draggable_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapDraggableChanged")}});r.addListener("streetview_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapStreetviewChanged")}});r.addListener("drag",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDrag",t)}}});r.addListener("dragend",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragEnd",t)}}});r.addListener("dragstart",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragStart",t)}}});r.addListener("resize",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i){let n={Width:r.getDiv().offsetWidth,Height:r.getDiv().offsetHeight};i.ref.invokeMethodAsync("MapResized",n)}}});r.addListener("tilesloaded",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTilesLoaded")}});r.addListener("idle",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapIdle")}});n[i].value.ref.invokeMethodAsync("MapInitialized",f)}}};let n=[],i=[]; \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MapsGoogle.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MapsGoogle.razor index 3bf184e5..2740e542 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MapsGoogle.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MapsGoogle.razor @@ -312,7 +312,10 @@ OnMapIdle="@OnMapIdle" ApiKey="@_googleMapsApiKey" /> - @*<GoogleMap Center="@_jsMapCenter" ApiKey="@_googleMapsApiKey" />*@ + @*<GoogleMap @bind-Center="_jsMapCenter" @bind-Center:event="OnMapCenterChanged" + @bind-Zoom="_jsMapZoomLevel" @bind-Zoom:event="OnMapZoomLevelChanged" + OnMapInitialized="@(() => {})" + ApiKey="@_googleMapsApiKey" />*@ </div> </div> From e77c135c8a2f611bd1b36e41703987c606bb8384 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sat, 27 Mar 2021 21:04:07 +0100 Subject: [PATCH 03/69] Created scroll components. --- .../Scroll/ScrollToPageBottom.razor | 36 ++++++++++++++++++ .../Scroll/ScrollToPageTop.razor | 37 +++++++++++++++++++ .../Components/JSInterop.razor | 18 ++++++++- 3 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor new file mode 100644 index 00000000..261d1953 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor @@ -0,0 +1,36 @@ +<div class="scrollToPageBottom" @onclick="async () => await _scrollHandler.ScrollToPageEndAsync()"> + @Content +</div> + +<style> + .scrollToPageBottom { + position: fixed; + top: 5rem; + right: 1.5rem; + z-index: 20; + cursor: pointer; + } +</style> + +@implements IAsyncDisposable +@inject IScrollHandler _scrollHandler; + +@code { + protected override async Task OnInitializedAsync() + { + await _scrollHandler.RegisterPageScrollAsync(async (e) => + { + + }); + } + + [Parameter] public RenderFragment Content { get; set; } + + public async ValueTask DisposeAsync() + { + if (_scrollHandler is not null) + { + await _scrollHandler.DisposeAsync(); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor new file mode 100644 index 00000000..75879d8c --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor @@ -0,0 +1,37 @@ +<div class="scrollToPageTop" @onclick="async () => await _scrollHandler.ScrollToPageTopAsync()"> + @Content +</div> + +<style> + .scrollToPageTop { + position: fixed; + bottom: 1.2rem; + right: 1.5rem; + z-index: 20; + cursor: pointer; + } +</style> + +@implements IAsyncDisposable + +@inject IScrollHandler _scrollHandler; + +@code { + protected override async Task OnInitializedAsync() + { + await _scrollHandler.RegisterPageScrollAsync(async (e) => + { + + }); + } + + [Parameter] public RenderFragment Content { get; set; } + + public async ValueTask DisposeAsync() + { + if (_scrollHandler is not null) + { + await _scrollHandler.DisposeAsync(); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor index a08f189f..25bbe4d1 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor @@ -14,7 +14,22 @@ <br /><strong>Majorsoft.Blazor.Components.Common.JsInterop</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Common.JsInterop" target="_blank">Nuget</a> </p> -@*Later add scroll components here*@ +<ScrollToPageBottom> + <Content> + <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> + <i class="fas fa-2x fa-chevron-down p-1"></i> + </div> + </Content> +</ScrollToPageBottom> + +<ScrollToPageTop> + <Content> + <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> + <i class="fas fa-2x fa-chevron-up p-1"></i> + </div> + </Content> +</ScrollToPageTop> + <hr /> <button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageEndAsync())">Scroll to Page bottom <i class="fas fa-arrow-down"></i></button> @@ -54,7 +69,6 @@ <HeadJs /> -@*Later add scroll components here*@ <hr /> <button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageTopAsync())">Scroll to Page top <i class="fas fa-arrow-up"></i></button> From 95da8b667f7e18f96d015c16771d1f9d0b28de20 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sat, 27 Mar 2021 21:33:16 +0100 Subject: [PATCH 04/69] Added new GetScreenSizeAsync() function. --- .github/docs/JsInterop.md | 5 ++++- .../Resize/IResizeHandler.cs | 8 +++++++- .../Resize/ResizeHandler.cs | 8 ++++++++ .../wwwroot/resize.js | 8 ++++++++ .../wwwroot/resize.min.js | 2 +- 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/.github/docs/JsInterop.md b/.github/docs/JsInterop.md index e0326515..131d78f0 100644 --- a/.github/docs/JsInterop.md +++ b/.github/docs/JsInterop.md @@ -152,7 +152,10 @@ Implements `IAsyncDisposable` interface the injected service should be Disposed. ### Functions - **`GetPageSizeAsync`**: **`Task<PageSize> GetPageSizeAsync()`**<br /> -Returns Browser Window size (height and width in Pixel). It is useful to call when page loaded, then use `RegisterPageResizeAsync` to get notifications +Returns Browser Window inner size (height and width in Pixel). It is useful to call when page loaded, then use `RegisterPageResizeAsync` to get notifications +on each page resize. +- **`GetScreenSizeAsync`**: **`Task<PageSize> GetScreenSizeAsync()`**<br /> +Returns Browser Window screen size (height and width in Pixel). It is useful to call when page loaded, then use `RegisterPageResizeAsync` to get notifications on each page resize. - **`RegisterPageResizeAsync`**: **`Task<string> RegisterPageResizeAsync(Func<ResizeEventArgs, Task> resizeCallback)`**<br /> Adds event listener for 'resize' HTML event for the whole document/window. diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Resize/IResizeHandler.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Resize/IResizeHandler.cs index 94dc3d1a..9c83c6b6 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Resize/IResizeHandler.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Resize/IResizeHandler.cs @@ -26,11 +26,17 @@ public interface IResizeHandler : IAsyncDisposable Task RemovePageResizeAsync(string eventId); /// <summary> - /// Returns Browser Window size (height and width in Pixel). + /// Returns Browser Window inner size (height and width in Pixel). /// </summary> /// <returns>Async Task with Window size</returns> Task<PageSize> GetPageSizeAsync(); + /// <summary> + /// Returns Browser Window screen size (height and width in Pixel). + /// </summary> + /// <returns>Async Task with Window size</returns> + Task<PageSize> GetScreenSizeAsync(); + /// <summary> /// Adds event listener for 'resize' HTML event for the given element with property filter. /// </summary> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Resize/ResizeHandler.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Resize/ResizeHandler.cs index e70ebf09..e4c68a16 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Resize/ResizeHandler.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Resize/ResizeHandler.cs @@ -57,6 +57,14 @@ public async Task<PageSize> GetPageSizeAsync() return size; } + public async Task<PageSize> GetScreenSizeAsync() + { + await CheckJsObjectAsync(); + + var size = await _resizeJs.InvokeAsync<PageSize>("getScreenSize"); + return size; + } + public async Task RegisterResizeAsync(ElementReference elementRef, Func<ResizeEventArgs, Task> resizeCallback = null) { await CheckJsObjectAsync(); diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/resize.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/resize.js index ab5a22ca..ea32b3db 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/resize.js +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/resize.js @@ -176,5 +176,13 @@ export function getPageSize() { Width: window.innerWidth } + return args; +} +export function getScreenSize() { + let args = { + Height: window.screen.height, + Width: window.screen.width + } + return args; } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/resize.min.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/resize.min.js index 42c82eb5..7551abaa 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/resize.min.js +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/resize.min.js @@ -1 +1 @@ -function i(n,t){return function(){var i=t.getBoundingClientRect();let r={Height:i.height,Width:i.width};n.invokeMethodAsync("ResizeEvent",r)}}function r(n,t,i){let r=!1;for(let i=0;i<n.length;i++)if(n[i].key===t){r=!0;break}r||n.push({key:t,handler:i})}function u(n,t){let i;for(let r=0;r<n.length;r++)if(n[r].key===t){i=n[r].handler;n.splice(r,1);break}return i}export function addResizeEventHandler(t,u){if(t&&u){for(let i=0;i<n.length;i++)if(n[i].key===t)return;let e=i(u,t),f=new ResizeObserver(e);f.observe(t);r(n,t,f)}}export function removeResizeEventHandler(t){if(t){let i=u(n,t);i&&(i.unobserve(t),i.disconnect())}}export function dispose(n){if(n)for(var t=0;t<n.length;t++)removeResizeEventHandler(n[t])}function f(n){return function(){n.invokeMethodAsync("ResizeEvent",getPageSize())}}function e(n,t,i){let r=!1;for(let i=0;i<n.length;i++)if(n[i].key===t){r=!0;break}r||n.push({key:t,handler:i})}function o(n,t){let i;for(let r=0;r<n.length;r++)if(n[r].key===t){i=n[r].handler;n.splice(r,1);break}return i}export function addGlobalResizeEvent(n,i){if(n&&i){let r=f(n);e(t,i,r);window.addEventListener("resize",r)}}export function removeGlobalResizeEvent(n){if(n){let i=o(t,n);i&&window.removeEventListener("resize",i)}}export function disposeGlobal(n){if(n)for(var t=0;t<n.length;t++)removeGlobalResizeEvent(n[t])}export function getPageSize(){return{Height:window.innerHeight,Width:window.innerWidth}}let n=[];let t=[]; \ No newline at end of file +function i(n,t){return function(){var i=t.getBoundingClientRect();let r={Height:i.height,Width:i.width};n.invokeMethodAsync("ResizeEvent",r)}}function r(n,t,i){let r=!1;for(let i=0;i<n.length;i++)if(n[i].key===t){r=!0;break}r||n.push({key:t,handler:i})}function u(n,t){let i;for(let r=0;r<n.length;r++)if(n[r].key===t){i=n[r].handler;n.splice(r,1);break}return i}export function addResizeEventHandler(t,u){if(t&&u){for(let i=0;i<n.length;i++)if(n[i].key===t)return;let e=i(u,t),f=new ResizeObserver(e);f.observe(t);r(n,t,f)}}export function removeResizeEventHandler(t){if(t){let i=u(n,t);i&&(i.unobserve(t),i.disconnect())}}export function dispose(n){if(n)for(var t=0;t<n.length;t++)removeResizeEventHandler(n[t])}function f(n){return function(){n.invokeMethodAsync("ResizeEvent",getPageSize())}}function e(n,t,i){let r=!1;for(let i=0;i<n.length;i++)if(n[i].key===t){r=!0;break}r||n.push({key:t,handler:i})}function o(n,t){let i;for(let r=0;r<n.length;r++)if(n[r].key===t){i=n[r].handler;n.splice(r,1);break}return i}export function addGlobalResizeEvent(n,i){if(n&&i){let r=f(n);e(t,i,r);window.addEventListener("resize",r)}}export function removeGlobalResizeEvent(n){if(n){let i=o(t,n);i&&window.removeEventListener("resize",i)}}export function disposeGlobal(n){if(n)for(var t=0;t<n.length;t++)removeGlobalResizeEvent(n[t])}export function getPageSize(){return{Height:window.innerHeight,Width:window.innerWidth}}export function getScreenSize(){return{Height:window.screen.height,Width:window.screen.width}}let n=[];let t=[]; \ No newline at end of file From 155efe4d60c6212f02c8fb36abcb45485a655458 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sat, 27 Mar 2021 22:35:51 +0100 Subject: [PATCH 05/69] Implemented scroll GetPageScrollSize() --- .github/docs/JsInterop.md | 6 +- ...oft.Blazor.Components.Common.JsInterop.xml | 61 ++++++++++++++++++- .../Scroll/IScrollHandler.cs | 9 ++- .../Scroll/ScrollEventArgs.cs | 9 +++ .../Scroll/ScrollHandler.cs | 9 ++- .../Scroll/ScrollResult.cs | 17 ++++++ .../Scroll/ScrollToPageBottom.razor | 14 ++++- .../Scroll/ScrollToPageTop.razor | 27 ++++++-- .../_Imports.razor | 4 +- .../wwwroot/scroll.js | 16 +++-- .../wwwroot/scroll.min.js | 2 +- 11 files changed, 153 insertions(+), 21 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollResult.cs diff --git a/.github/docs/JsInterop.md b/.github/docs/JsInterop.md index 131d78f0..e0863ccc 100644 --- a/.github/docs/JsInterop.md +++ b/.github/docs/JsInterop.md @@ -124,8 +124,10 @@ Scrolls to top of the page (X top). Scrolls to X position on the page. - **`ScrollToPageYAsync`**: **`Task ScrollToPageYAsync(double y)`**<br /> Scrolls to Y position on the page. -- **`GetPageScrollPosAsync`**: **`Task<ScrollEventArgs> GetPageScrollPosAsync()`**<br /> -Returns page X,Y scroll position as `ScrollEventArgs`. +- **`GetPageScrollPosAsync`**: **`Task<ScrollResult> GetPageScrollPosAsync()`**<br /> +Returns page X,Y scroll current position as `ScrollResult`. +- **`GetPageScrollSizeAsync`**: **`Task<ScrollResult> GetPageScrollSizeAsync()`**<br /> +Returns page X,Y scroll size (max values) as `ScrollResult`. - **`RegisterPageScrollAsync`**: **`Task<string> RegisterPageScrollAsync(Func<ScrollEventArgs, Task> scrollCallback)`**<br /> Adds event listener for 'scroll' HTML event for the whole document/window. **Also returns with Event Id event id to unsubscribe from event.** - **`RemovePageScrollAsync`**: **`Task RemovePageScrollAsync(string eventId)`**<br /> 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 f1185219..e9c0a1e1 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 @@ -525,7 +525,13 @@ </member> <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Resize.IResizeHandler.GetPageSizeAsync"> <summary> - Returns Browser Window size (height and width in Pixel). + Returns Browser Window inner size (height and width in Pixel). + </summary> + <returns>Async Task with Window size</returns> + </member> + <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Resize.IResizeHandler.GetScreenSizeAsync"> + <summary> + Returns Browser Window screen size (height and width in Pixel). </summary> <returns>Async Task with Window size</returns> </member> @@ -718,7 +724,13 @@ </member> <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.GetPageScrollPosAsync"> <summary> - Returns page X,Y scroll position as <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollEventArgs"/>. + Returns page X,Y scroll current position as <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollResult"/>. + </summary> + <returns>Async Task</returns> + </member> + <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.GetPageScrollSizeAsync"> + <summary> + Returns page X,Y scroll size (max values) as <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollResult"/>. </summary> <returns>Async Task</returns> </member> @@ -741,10 +753,55 @@ PageScrollEventInfo event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback </summary> </member> + <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollEventArgs"> + <summary> + Scroll event args + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollEventArgs.X"> + <summary> + Scroll X position + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollEventArgs.Y"> + <summary> + Scroll Y position + </summary> + </member> <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollHandler"> <summary> Implementation of <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler"/> </summary> </member> + <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollResult"> + <summary> + Scroll result + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollResult.X"> + <summary> + Scroll X value + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollResult.Y"> + <summary> + Scroll Y value + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.Content"> + <summary> + + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.Content"> + <summary> + + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.VisibleFromPagePercentage"> + <summary> + + </summary> + </member> </members> </doc> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs index 6cbe7b32..75e7c948 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs @@ -52,10 +52,15 @@ public interface IScrollHandler : IAsyncDisposable /// <returns>Async Task</returns> Task ScrollToPageYAsync(double y); /// <summary> - /// Returns page X,Y scroll position as <see cref="ScrollEventArgs"/>. + /// Returns page X,Y scroll current position as <see cref="ScrollResult"/>. /// </summary> /// <returns>Async Task</returns> - Task<ScrollEventArgs> GetPageScrollPosAsync(); + Task<ScrollResult> GetPageScrollPosAsync(); + /// <summary> + /// Returns page X,Y scroll size (max values) as <see cref="ScrollResult"/>. + /// </summary> + /// <returns>Async Task</returns> + Task<ScrollResult> GetPageScrollSizeAsync(); /// <summary> /// Adds event listener for 'scroll' HTML event for the whole document/window. diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollEventArgs.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollEventArgs.cs index 7fe2a35c..a494ba8c 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollEventArgs.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollEventArgs.cs @@ -2,9 +2,18 @@ namespace Majorsoft.Blazor.Components.Common.JsInterop.Scroll { + /// <summary> + /// Scroll event args + /// </summary> public sealed class ScrollEventArgs : EventArgs { + /// <summary> + /// Scroll X position + /// </summary> public double X { get; set; } + /// <summary> + /// Scroll Y position + /// </summary> public double Y { get; set; } } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs index 32b8c8d0..d2b43947 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs @@ -43,10 +43,15 @@ public async Task ScrollToPageYAsync(double y) await CheckJsObjectAsync(); await _scrollJs.InvokeVoidAsync("scrollToPageY", y); } - public async Task<ScrollEventArgs> GetPageScrollPosAsync() + public async Task<ScrollResult> GetPageScrollPosAsync() { await CheckJsObjectAsync(); - return await _scrollJs.InvokeAsync<ScrollEventArgs>("getPageScrollPosition"); + return await _scrollJs.InvokeAsync<ScrollResult>("getPageScrollPosition"); + } + public async Task<ScrollResult> GetPageScrollSizeAsync() + { + await CheckJsObjectAsync(); + return await _scrollJs.InvokeAsync<ScrollResult>("getPageScrollSize"); } public async Task ScrollToElementAsync(ElementReference elementReference) diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollResult.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollResult.cs new file mode 100644 index 00000000..8461d601 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollResult.cs @@ -0,0 +1,17 @@ +namespace Majorsoft.Blazor.Components.Common.JsInterop.Scroll +{ + /// <summary> + /// Scroll result + /// </summary> + public sealed class ScrollResult + { + /// <summary> + /// Scroll X value + /// </summary> + public double X { get; set; } + /// <summary> + /// Scroll Y value + /// </summary> + public double Y { get; set; } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor index 261d1953..22566c60 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor @@ -1,6 +1,9 @@ -<div class="scrollToPageBottom" @onclick="async () => await _scrollHandler.ScrollToPageEndAsync()"> - @Content -</div> +@if (_isVisible) +{ + <div class="scrollToPageBottom" @onclick="async () => await _scrollHandler.ScrollToPageEndAsync()"> + @Content + </div> +} <style> .scrollToPageBottom { @@ -16,6 +19,8 @@ @inject IScrollHandler _scrollHandler; @code { + private bool _isVisible = false; + protected override async Task OnInitializedAsync() { await _scrollHandler.RegisterPageScrollAsync(async (e) => @@ -24,6 +29,9 @@ }); } + /// <summary> + /// + /// </summary> [Parameter] public RenderFragment Content { get; set; } public async ValueTask DisposeAsync() diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor index 75879d8c..585ef0cb 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor @@ -1,6 +1,9 @@ -<div class="scrollToPageTop" @onclick="async () => await _scrollHandler.ScrollToPageTopAsync()"> - @Content -</div> +@if (_isVisible) +{ + <div class="scrollToPageTop" @onclick="async () => await _scrollHandler.ScrollToPageTopAsync()"> + @Content + </div> +} <style> .scrollToPageTop { @@ -14,19 +17,35 @@ @implements IAsyncDisposable -@inject IScrollHandler _scrollHandler; +@inject IScrollHandler _scrollHandler +@inject ILogger<ScrollToPageTop> _logger @code { + private bool _isVisible = false; + protected override async Task OnInitializedAsync() { await _scrollHandler.RegisterPageScrollAsync(async (e) => { + var scrollSize = await _scrollHandler.GetPageScrollSizeAsync(); + var percentage = (e.Y / scrollSize.Y) * 100; + _isVisible = percentage >= VisibleFromPagePercentage; + StateHasChanged(); }); } + /// <summary> + /// + /// </summary> [Parameter] public RenderFragment Content { get; set; } + /// <summary> + /// + /// </summary> + [Parameter] public byte VisibleFromPagePercentage { get; set; } = 30; + + public async ValueTask DisposeAsync() { if (_scrollHandler is not null) diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/_Imports.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/_Imports.razor index 984befec..26a6b3c6 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/_Imports.razor @@ -1,4 +1,6 @@ @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Web @using Microsoft.Extensions.Logging; -@using Microsoft.JSInterop \ No newline at end of file +@using Microsoft.JSInterop + +@using Majorsoft.Blazor.Components.Common.JsInterop.Resize \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js index 5447fd7c..b7ef8a4b 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js @@ -124,8 +124,16 @@ export function getPageScrollPosition() { let left = window.pageXOffset || document.documentElement.scrollLeft; let args = { - X: top, - Y: left + X: left, + Y: top + }; + + return args; +} +export function getPageScrollSize() { + let args = { + X: document.body.scrollWidth, + Y: document.body.scrollHeight }; return args; @@ -138,8 +146,8 @@ function createScrollEventHandler(dotnetRef) { let left = window.pageXOffset || document.documentElement.scrollLeft; let args = { - X: top, - Y: left + X: left, + Y: top }; dotnetRef.invokeMethodAsync("PageScroll", args); 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 e247edbf..9519f673 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<n.offsetParent.scrollTop}export function scrollToEnd(t){t&&typeof t.scrollTop!==n&&typeof t.scrollHeight!==n&&(t.scrollTop=t.scrollHeight)}export function scrollToTop(t){t&&typeof t.scrollTop!==n&&(t.scrollTop=0)}export function scrollToX(t,i){t&&typeof t.scrollTop!==n&&(t.scrollTop=i)}export function scrollToY(t,i){t&&typeof t.scrollLeft!==n&&(t.scrollLeft=i)}export function getScrollXPosition(t){if(t&&typeof t.scrollTop!==n)return t.scrollTop}export function scrollToPageEnd(){document.body.scrollTop=document.body.scrollHeight;document.documentElement.scrollTop=document.body.scrollHeight}export function scrollToPageTop(){document.body.scrollTop=0;document.documentElement.scrollTop=0}export function scrollToPageX(n){document.body.scrollTop=n;document.documentElement.scrollTop=n}export function scrollToPageY(n){document.body.scrollLeft=n;document.documentElement.scrollLeft=n}export function getPageScrollPosition(){let n=window.pageYOffset||document.documentElement.scrollTop,t=window.pageXOffset||document.documentElement.scrollLeft;return{X:n,Y:t}}function i(n){return function(){let t=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,r={X:t,Y:i};n.invokeMethodAsync("PageScroll",r)}}function r(n,t,i){let r=!1;for(let i=0;i<n.length;i++)if(n[i].key===t){r=!0;break}r||n.push({key:t,handler:i})}function u(n,t){let i;for(let r=0;r<n.length;r++)if(n[r].key===t){i=n[r].handler;n.splice(r,1);break}return i}export function addScrollEvent(n,u){if(n&&u){let f=i(n);r(t,u,f);window.addEventListener("scroll",f,!1)}}export function removeScrollEvent(n){if(n){let i=u(t,n);i&&window.removeEventListener("scroll",i,!1)}}export function dispose(n){if(n)for(var t=0;t<n.length;t++)removeScrollEvent(n[t])}let n="undefined";let t=[]; \ No newline at end of file +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<n.offsetParent.scrollTop}export function scrollToEnd(t){t&&typeof t.scrollTop!==n&&typeof t.scrollHeight!==n&&(t.scrollTop=t.scrollHeight)}export function scrollToTop(t){t&&typeof t.scrollTop!==n&&(t.scrollTop=0)}export function scrollToX(t,i){t&&typeof t.scrollTop!==n&&(t.scrollTop=i)}export function scrollToY(t,i){t&&typeof t.scrollLeft!==n&&(t.scrollLeft=i)}export function getScrollXPosition(t){if(t&&typeof t.scrollTop!==n)return t.scrollTop}export function scrollToPageEnd(){document.body.scrollTop=document.body.scrollHeight;document.documentElement.scrollTop=document.body.scrollHeight}export function scrollToPageTop(){document.body.scrollTop=0;document.documentElement.scrollTop=0}export function scrollToPageX(n){document.body.scrollTop=n;document.documentElement.scrollTop=n}export function scrollToPageY(n){document.body.scrollLeft=n;document.documentElement.scrollLeft=n}export function getPageScrollPosition(){let n=window.pageYOffset||document.documentElement.scrollTop,t=window.pageXOffset||document.documentElement.scrollLeft;return{X:t,Y:n}}export function getPageScrollSize(){return{X:document.body.scrollWidth,Y:document.body.scrollHeight}}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<n.length;i++)if(n[i].key===t){r=!0;break}r||n.push({key:t,handler:i})}function u(n,t){let i;for(let r=0;r<n.length;r++)if(n[r].key===t){i=n[r].handler;n.splice(r,1);break}return i}export function addScrollEvent(n,u){if(n&&u){let f=i(n);r(t,u,f);window.addEventListener("scroll",f,!1)}}export function removeScrollEvent(n){if(n){let i=u(t,n);i&&window.removeEventListener("scroll",i,!1)}}export function dispose(n){if(n)for(var t=0;t<n.length;t++)removeScrollEvent(n[t])}let n="undefined";let t=[]; \ No newline at end of file From e34282852278370d5043b2b21624bb4cf48b8193 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sat, 27 Mar 2021 22:57:32 +0100 Subject: [PATCH 06/69] Added smooth scroll feature to page scroll. --- .github/docs/JsInterop.md | 8 ++-- ...oft.Blazor.Components.Common.JsInterop.xml | 26 ++++++++--- .../Scroll/IScrollHandler.cs | 14 +++--- .../Scroll/ScrollHandler.cs | 16 +++---- .../Scroll/ScrollToPageBottom.razor | 9 +++- .../Scroll/ScrollToPageTop.razor | 37 +++++++++++----- .../wwwroot/scroll.js | 44 ++++++++++++++----- .../wwwroot/scroll.min.js | 2 +- 8 files changed, 107 insertions(+), 49 deletions(-) diff --git a/.github/docs/JsInterop.md b/.github/docs/JsInterop.md index e0863ccc..efc941b1 100644 --- a/.github/docs/JsInterop.md +++ b/.github/docs/JsInterop.md @@ -116,13 +116,13 @@ Scrolls the given element into the page view area. Finds element by Id and scrolls the given element into the page view area. - **`ScrollToElementByNameAsync`**: **`Task ScrollToElementByNameAsync(string name)`**<br /> Finds element by name and scrolls the given element into the page view area. -- **`ScrollToPageEndAsync`**: **`Task ScrollToPageEndAsync()`**<br /> +- **`ScrollToPageEndAsync`**: **`Task ScrollToPageEndAsync(bool smooth)`**<br /> Scrolls to end of the page (X bottom). -- **`ScrollToPageTopAsync`**: **`Task ScrollToPageTopAsync()`**<br /> +- **`ScrollToPageTopAsync`**: **`Task ScrollToPageTopAsync(bool smooth)`**<br /> Scrolls to top of the page (X top). -- **`ScrollToPageXAsync`**: **`Task ScrollToPageXAsync(double x)`**<br /> +- **`ScrollToPageXAsync`**: **`Task ScrollToPageXAsync(double x, bool smooth)`**<br /> Scrolls to X position on the page. -- **`ScrollToPageYAsync`**: **`Task ScrollToPageYAsync(double y)`**<br /> +- **`ScrollToPageYAsync`**: **`Task ScrollToPageYAsync(double y, bool smooth)`**<br /> Scrolls to Y position on the page. - **`GetPageScrollPosAsync`**: **`Task<ScrollResult> GetPageScrollPosAsync()`**<br /> Returns page X,Y scroll current position as `ScrollResult`. 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 e9c0a1e1..15416f9b 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 @@ -696,30 +696,34 @@ <param name="name">DOM element name</param> <returns>Async Task</returns> </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageEndAsync"> + <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageEndAsync(System.Boolean)"> <summary> Scrolls to end of the page (X bottom). </summary> + <param name="smooth">Scroll should jump or smoothly scroll</param> <returns>Async Task</returns> </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageTopAsync"> + <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageTopAsync(System.Boolean)"> <summary> Scrolls to top of the page (X top). </summary> + <param name="smooth">Scroll should jump or smoothly scroll</param> <returns>Async Task</returns> </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageXAsync(System.Double)"> + <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageXAsync(System.Double,System.Boolean)"> <summary> Scrolls to X position on the page. </summary> <param name="x">Scroll top x value</param> + <param name="smooth">Scroll should jump or smoothly scroll</param> <returns>Async Task</returns> </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageYAsync(System.Double)"> + <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageYAsync(System.Double,System.Boolean)"> <summary> Scrolls to Y position on the page. </summary> - <param name="x">Scroll top x value</param> + <param name="y">Scroll top x value</param> + <param name="smooth">Scroll should jump or smoothly scroll</param> <returns>Async Task</returns> </member> <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.GetPageScrollPosAsync"> @@ -793,12 +797,22 @@ </summary> </member> - <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.Content"> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.SmootScroll"> <summary> </summary> </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.Content"> + <summary> + + </summary> + </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.VisibleFromPagePercentage"> + <summary> + + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.SmootScroll"> <summary> </summary> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs index 75e7c948..a4526af3 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/IScrollHandler.cs @@ -32,25 +32,29 @@ public interface IScrollHandler : IAsyncDisposable /// <summary> /// Scrolls to end of the page (X bottom). /// </summary> + /// <param name="smooth">Scroll should jump or smoothly scroll</param> /// <returns>Async Task</returns> - Task ScrollToPageEndAsync(); + Task ScrollToPageEndAsync(bool smooth = false); /// <summary> /// Scrolls to top of the page (X top). /// </summary> + /// <param name="smooth">Scroll should jump or smoothly scroll</param> /// <returns>Async Task</returns> - Task ScrollToPageTopAsync(); + Task ScrollToPageTopAsync(bool smooth = false); /// <summary> /// Scrolls to X position on the page. /// </summary> /// <param name="x">Scroll top x value</param> + /// <param name="smooth">Scroll should jump or smoothly scroll</param> /// <returns>Async Task</returns> - Task ScrollToPageXAsync(double x); + Task ScrollToPageXAsync(double x, bool smooth = false); /// <summary> /// Scrolls to Y position on the page. /// </summary> - /// <param name="x">Scroll top x value</param> + /// <param name="y">Scroll top x value</param> + /// <param name="smooth">Scroll should jump or smoothly scroll</param> /// <returns>Async Task</returns> - Task ScrollToPageYAsync(double y); + Task ScrollToPageYAsync(double y, bool smooth = false); /// <summary> /// Returns page X,Y scroll current position as <see cref="ScrollResult"/>. /// </summary> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs index d2b43947..5706773d 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollHandler.cs @@ -23,25 +23,25 @@ public ScrollHandler(IJSRuntime jsRuntime) _dotNetObjectReferences = new List<DotNetObjectReference<PageScrollEventInfo>>(); } - public async Task ScrollToPageEndAsync() + public async Task ScrollToPageEndAsync(bool smooth) { await CheckJsObjectAsync(); - await _scrollJs.InvokeVoidAsync("scrollToPageEnd"); + await _scrollJs.InvokeVoidAsync("scrollToPageEnd", smooth); } - public async Task ScrollToPageTopAsync() + public async Task ScrollToPageTopAsync(bool smooth) { await CheckJsObjectAsync(); - await _scrollJs.InvokeVoidAsync("scrollToPageTop"); + await _scrollJs.InvokeVoidAsync("scrollToPageTop", smooth); } - public async Task ScrollToPageXAsync(double x) + public async Task ScrollToPageXAsync(double x, bool smooth) { await CheckJsObjectAsync(); - await _scrollJs.InvokeVoidAsync("scrollToPageX", x); + await _scrollJs.InvokeVoidAsync("scrollToPageX", x, smooth); } - public async Task ScrollToPageYAsync(double y) + public async Task ScrollToPageYAsync(double y, bool smooth) { await CheckJsObjectAsync(); - await _scrollJs.InvokeVoidAsync("scrollToPageY", y); + await _scrollJs.InvokeVoidAsync("scrollToPageY", y, smooth); } public async Task<ScrollResult> GetPageScrollPosAsync() { diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor index 22566c60..104b6643 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor @@ -1,6 +1,6 @@ @if (_isVisible) { - <div class="scrollToPageBottom" @onclick="async () => await _scrollHandler.ScrollToPageEndAsync()"> + <div class="scrollToPageBottom" @onclick="async () => await _scrollHandler.ScrollToPageEndAsync(SmootScroll)"> @Content </div> } @@ -19,7 +19,7 @@ @inject IScrollHandler _scrollHandler; @code { - private bool _isVisible = false; + private bool _isVisible = true; protected override async Task OnInitializedAsync() { @@ -34,6 +34,11 @@ /// </summary> [Parameter] public RenderFragment Content { get; set; } + /// <summary> + /// + /// </summary> + [Parameter] public bool SmootScroll { get; set; } = true; + public async ValueTask DisposeAsync() { if (_scrollHandler is not null) diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor index 585ef0cb..2192d329 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor @@ -1,9 +1,6 @@ -@if (_isVisible) -{ - <div class="scrollToPageTop" @onclick="async () => await _scrollHandler.ScrollToPageTopAsync()"> - @Content - </div> -} +<div class="scrollToPageTop" @onclick="async () => await _scrollHandler.ScrollToPageTopAsync(SmootScroll)"> + @Content +</div> <style> .scrollToPageTop { @@ -12,6 +9,8 @@ right: 1.5rem; z-index: 20; cursor: pointer; + opacity: @(_isVisible ? 1 : 0); + transition: opacity 0.2s linear; } </style> @@ -20,8 +19,20 @@ @inject IScrollHandler _scrollHandler @inject ILogger<ScrollToPageTop> _logger -@code { +@code { private bool _isVisible = false; + private bool IsVisible + { + get => _isVisible; + set + { + if (value != _isVisible) + { + _isVisible = value; + StateHasChanged(); + } + } + } protected override async Task OnInitializedAsync() { @@ -30,21 +41,25 @@ var scrollSize = await _scrollHandler.GetPageScrollSizeAsync(); var percentage = (e.Y / scrollSize.Y) * 100; - _isVisible = percentage >= VisibleFromPagePercentage; - StateHasChanged(); + IsVisible = percentage >= VisibleFromPagePercentage; }); } /// <summary> - /// + /// /// </summary> [Parameter] public RenderFragment Content { get; set; } /// <summary> - /// + /// /// </summary> [Parameter] public byte VisibleFromPagePercentage { get; set; } = 30; + /// <summary> + /// + /// </summary> + [Parameter] public bool SmootScroll { get; set; } = true; + public async ValueTask DisposeAsync() { diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js index b7ef8a4b..293eaa83 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js @@ -103,21 +103,41 @@ export function getScrollXPosition(element) { /* Injectable functions */ //HTLM page scroll -export function scrollToPageEnd() { - document.body.scrollTop = document.body.scrollHeight; - document.documentElement.scrollTop = document.body.scrollHeight; +export function scrollToPageEnd(smooth) { + if (!smooth) { + document.body.scrollTop = document.body.scrollHeight; + document.documentElement.scrollTop = document.body.scrollHeight; + } + else { + window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); + } } -export function scrollToPageTop() { - document.body.scrollTop = 0; - document.documentElement.scrollTop = 0; +export function scrollToPageTop(smooth) { + if (!smooth) { + document.body.scrollTop = 0; + document.documentElement.scrollTop = 0; + } + else { + window.scrollTo({ top: 0, behavior: 'smooth' }); + } } -export function scrollToPageX(x) { - document.body.scrollTop = x; - document.documentElement.scrollTop = x; +export function scrollToPageX(x, smooth) { + if (!smooth) { + document.body.scrollLeft = x; + document.documentElement.scrollLeft = x; + } + else { + window.scrollTo({ left: 0, behavior: 'smooth' }); + } } -export function scrollToPageY(y) { - document.body.scrollLeft = y; - document.documentElement.scrollLeft = y; +export function scrollToPageY(y, smooth) { + if (!smooth) { + document.body.scrollTop = y; + document.documentElement.scrollTop = y; + } + else { + window.scrollTo({ top: y, behavior: 'smooth' }); + } } export function getPageScrollPosition() { let top = window.pageYOffset || document.documentElement.scrollTop; 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 9519f673..9d0115a0 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<n.offsetParent.scrollTop}export function scrollToEnd(t){t&&typeof t.scrollTop!==n&&typeof t.scrollHeight!==n&&(t.scrollTop=t.scrollHeight)}export function scrollToTop(t){t&&typeof t.scrollTop!==n&&(t.scrollTop=0)}export function scrollToX(t,i){t&&typeof t.scrollTop!==n&&(t.scrollTop=i)}export function scrollToY(t,i){t&&typeof t.scrollLeft!==n&&(t.scrollLeft=i)}export function getScrollXPosition(t){if(t&&typeof t.scrollTop!==n)return t.scrollTop}export function scrollToPageEnd(){document.body.scrollTop=document.body.scrollHeight;document.documentElement.scrollTop=document.body.scrollHeight}export function scrollToPageTop(){document.body.scrollTop=0;document.documentElement.scrollTop=0}export function scrollToPageX(n){document.body.scrollTop=n;document.documentElement.scrollTop=n}export function scrollToPageY(n){document.body.scrollLeft=n;document.documentElement.scrollLeft=n}export function getPageScrollPosition(){let n=window.pageYOffset||document.documentElement.scrollTop,t=window.pageXOffset||document.documentElement.scrollLeft;return{X:t,Y:n}}export function getPageScrollSize(){return{X:document.body.scrollWidth,Y:document.body.scrollHeight}}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<n.length;i++)if(n[i].key===t){r=!0;break}r||n.push({key:t,handler:i})}function u(n,t){let i;for(let r=0;r<n.length;r++)if(n[r].key===t){i=n[r].handler;n.splice(r,1);break}return i}export function addScrollEvent(n,u){if(n&&u){let f=i(n);r(t,u,f);window.addEventListener("scroll",f,!1)}}export function removeScrollEvent(n){if(n){let i=u(t,n);i&&window.removeEventListener("scroll",i,!1)}}export function dispose(n){if(n)for(var t=0;t<n.length;t++)removeScrollEvent(n[t])}let n="undefined";let t=[]; \ No newline at end of file +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<n.offsetParent.scrollTop}export function scrollToEnd(t){t&&typeof t.scrollTop!==n&&typeof t.scrollHeight!==n&&(t.scrollTop=t.scrollHeight)}export function scrollToTop(t){t&&typeof t.scrollTop!==n&&(t.scrollTop=0)}export function scrollToX(t,i){t&&typeof t.scrollTop!==n&&(t.scrollTop=i)}export function scrollToY(t,i){t&&typeof t.scrollLeft!==n&&(t.scrollLeft=i)}export function getScrollXPosition(t){if(t&&typeof t.scrollTop!==n)return t.scrollTop}export function scrollToPageEnd(n){n?window.scrollTo({top:document.body.scrollHeight,behavior:"smooth"}):(document.body.scrollTop=document.body.scrollHeight,document.documentElement.scrollTop=document.body.scrollHeight)}export function scrollToPageTop(n){n?window.scrollTo({top:0,behavior:"smooth"}):(document.body.scrollTop=0,document.documentElement.scrollTop=0)}export function scrollToPageX(n,t){t?window.scrollTo({left:0,behavior:"smooth"}):(document.body.scrollLeft=n,document.documentElement.scrollLeft=n)}export function scrollToPageY(n,t){t?window.scrollTo({top:n,behavior:"smooth"}):(document.body.scrollTop=n,document.documentElement.scrollTop=n)}export function getPageScrollPosition(){let n=window.pageYOffset||document.documentElement.scrollTop,t=window.pageXOffset||document.documentElement.scrollLeft;return{X:t,Y:n}}export function getPageScrollSize(){return{X:document.body.scrollWidth,Y:document.body.scrollHeight}}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<n.length;i++)if(n[i].key===t){r=!0;break}r||n.push({key:t,handler:i})}function u(n,t){let i;for(let r=0;r<n.length;r++)if(n[r].key===t){i=n[r].handler;n.splice(r,1);break}return i}export function addScrollEvent(n,u){if(n&&u){let f=i(n);r(t,u,f);window.addEventListener("scroll",f,!1)}}export function removeScrollEvent(n){if(n){let i=u(t,n);i&&window.removeEventListener("scroll",i,!1)}}export function dispose(n){if(n)for(var t=0;t<n.length;t++)removeScrollEvent(n[t])}let n="undefined";let t=[]; \ No newline at end of file From c0b7241cae96e1447655089fb078d0c60cc5b6cd Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sun, 28 Mar 2021 13:26:26 +0200 Subject: [PATCH 07/69] Implemented ScrollToPageTop component. --- ...oft.Blazor.Components.Common.JsInterop.xml | 49 +++++++++++-- .../Scroll/PageScrollHorizontalOrientation.cs | 17 +++++ .../Scroll/ScrollToPageTop.razor | 69 ++++++++++++------- .../Scroll/ScrollToPageTop.razor.css | 6 ++ .../Scroll/ScrollToPageTop.razor.min.css | 1 + .../bundleconfig.json | 6 ++ .../Components/JSInterop.razor | 28 +++----- .../Components/JsDemo/ScrollJs.razor | 19 ++++- 8 files changed, 146 insertions(+), 49 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/PageScrollHorizontalOrientation.cs create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css 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 15416f9b..94211681 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 @@ -757,6 +757,21 @@ PageScrollEventInfo event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback </summary> </member> + <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.PageScrollHorizontalOrientation"> + <summary> + Page scroll elements orientation on page <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom"/> and <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop"/> elements + </summary> + </member> + <member name="F:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.PageScrollHorizontalOrientation.Right"> + <summary> + Element placed to the Right side + </summary> + </member> + <member name="F:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.PageScrollHorizontalOrientation.Left"> + <summary> + Element placed to the Left side + </summary> + </member> <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollEventArgs"> <summary> Scroll event args @@ -802,19 +817,39 @@ </summary> </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.InnerElementReference"> + <summary> + Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + </summary> + </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.Content"> - <summary> - - </summary> + <summary> + HTML Content of the scroll panel. + </summary> </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.VisibleFromPagePercentage"> - <summary> - - </summary> + <summary> + Element should be visible when scroll reached page % of given value. + </summary> </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.SmootScroll"> <summary> - + Scroll shold be jump or smoothly scroll. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.PaddingFromBottom"> + <summary> + Required space from page bottom in px. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.PaddingFromSide"> + <summary> + Required space from page (left/right) side in px. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.HorizontalOrientation"> + <summary> + Element orientation on page. </summary> </member> </members> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/PageScrollHorizontalOrientation.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/PageScrollHorizontalOrientation.cs new file mode 100644 index 00000000..0c62e6e0 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/PageScrollHorizontalOrientation.cs @@ -0,0 +1,17 @@ +namespace Majorsoft.Blazor.Components.Common.JsInterop.Scroll +{ + /// <summary> + /// Page scroll elements orientation on page <see cref="ScrollToPageBottom"/> and <see cref="ScrollToPageTop"/> elements + /// </summary> + public enum PageScrollHorizontalOrientation + { + /// <summary> + /// Element placed to the Right side + /// </summary> + Right, + /// <summary> + /// Element placed to the Left side + /// </summary> + Left + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor index 2192d329..3ad108ba 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor @@ -1,16 +1,18 @@ -<div class="scrollToPageTop" @onclick="async () => await _scrollHandler.ScrollToPageTopAsync(SmootScroll)"> +<div @ref="_inputRef" class="scrollToPageTop @HorizontalOrientation.ToString().ToLower()" tabindex="500" + @onclick="async () => await _scrollHandler.ScrollToPageTopAsync(SmootScroll)"> @Content </div> <style> .scrollToPageTop { - position: fixed; - bottom: 1.2rem; - right: 1.5rem; - z-index: 20; - cursor: pointer; + bottom: @(PaddingFromBottom)px; opacity: @(_isVisible ? 1 : 0); - transition: opacity 0.2s linear; + } + .scrollToPageTop.right { + right: @(PaddingFromSide)px; + } + .scrollToPageTop.left { + left: @(PaddingFromSide)px; } </style> @@ -21,45 +23,66 @@ @code { private bool _isVisible = false; - private bool IsVisible - { - get => _isVisible; - set - { - if (value != _isVisible) - { - _isVisible = value; - StateHasChanged(); - } - } - } protected override async Task OnInitializedAsync() { + WriteDiag("Component initialized..."); + await _scrollHandler.RegisterPageScrollAsync(async (e) => { var scrollSize = await _scrollHandler.GetPageScrollSizeAsync(); var percentage = (e.Y / scrollSize.Y) * 100; - IsVisible = percentage >= VisibleFromPagePercentage; + var visible = percentage >= VisibleFromPagePercentage; + if (visible != _isVisible) + { + _isVisible = visible; + StateHasChanged(); + + WriteDiag($"Component visibilit changed visible: {_isVisible}."); + } }); } + private ElementReference _inputRef; /// <summary> - /// + /// Exposes a Blazor <see cref="ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + /// </summary> + public ElementReference InnerElementReference => _inputRef; + + /// <summary> + /// HTML Content of the scroll panel. /// </summary> [Parameter] public RenderFragment Content { get; set; } /// <summary> - /// + /// Element should be visible when scroll reached page % of given value. /// </summary> [Parameter] public byte VisibleFromPagePercentage { get; set; } = 30; /// <summary> - /// + /// Scroll shold be jump or smoothly scroll. /// </summary> [Parameter] public bool SmootScroll { get; set; } = true; + /// <summary> + /// Required space from page bottom in px. + /// </summary> + [Parameter] public int PaddingFromBottom { get; set; } = 20; + /// <summary> + /// Required space from page (left/right) side in px. + /// </summary> + [Parameter] public int PaddingFromSide { get; set; } = 24; + /// <summary> + /// Element orientation on page. + /// </summary> + [Parameter] public PageScrollHorizontalOrientation HorizontalOrientation { get; set; } = PageScrollHorizontalOrientation.Right; + + + private void WriteDiag(string message) + { + _logger.LogDebug($"Component {this.GetType()}: {message}"); + } public async ValueTask DisposeAsync() { diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css new file mode 100644 index 00000000..ab507144 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css @@ -0,0 +1,6 @@ +.scrollToPageTop { + position: fixed; + z-index: 20; + cursor: pointer; + transition: opacity 0.25s linear; +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css new file mode 100644 index 00000000..dbcc64b4 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css @@ -0,0 +1 @@ +.scrollToPageTop{position:fixed;z-index:20;cursor:pointer;transition:opacity .25s linear} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json b/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json index 24d82cd4..48ca414e 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json @@ -58,5 +58,11 @@ "inputFiles": [ "wwwroot/head.js" ] + }, + { + "outputFileName": "Scroll/ScrollToPageTop.razor.min.css", + "inputFiles": [ + "Scroll/ScrollToPageTop.razor.css" + ] } ] diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor index 25bbe4d1..f357bc21 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor @@ -14,24 +14,11 @@ <br /><strong>Majorsoft.Blazor.Components.Common.JsInterop</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Common.JsInterop" target="_blank">Nuget</a> </p> -<ScrollToPageBottom> - <Content> - <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> - <i class="fas fa-2x fa-chevron-down p-1"></i> - </div> - </Content> -</ScrollToPageBottom> - -<ScrollToPageTop> - <Content> - <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> - <i class="fas fa-2x fa-chevron-up p-1"></i> - </div> - </Content> -</ScrollToPageTop> - <hr /> -<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageEndAsync())">Scroll to Page bottom <i class="fas fa-arrow-down"></i></button> +@*Scroll to page end with ScrollHandler for custom floating component see ScrollToPageBottom*@ +<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageEndAsync(_smmothScroll))">Scroll to Page bottom <i class="fas fa-arrow-down"></i></button> +Smooth scroll: +<input type="checkbox" @bind="_smmothScroll" /> <div> <h3>JS Interop features:</h3> @@ -70,7 +57,10 @@ <HeadJs /> <hr /> -<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageTopAsync())">Scroll to Page top <i class="fas fa-arrow-up"></i></button> +@*Scroll to page end with ScrollHandler for custom floating component see ScrollToPageTop*@ +<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageTopAsync(_smmothScroll))">Scroll to Page top <i class="fas fa-arrow-up"></i></button> +Smooth scroll: +<input type="checkbox" @bind="_smmothScroll" /> @implements IAsyncDisposable @inject IScrollHandler _scrollHandler @@ -83,4 +73,6 @@ await _scrollHandler.DisposeAsync(); } } + + private bool _smmothScroll = true; } \ No newline at end of file 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 2a6ebd63..c2f567dd 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor @@ -1,4 +1,21 @@ -<div class="container-fluid p-3 mb-3 border rounded"> +@*Scroll on page components, can be placed anywhere on a page but should be used one per page*@ +<ScrollToPageBottom> + <Content> + <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> + <i class="fas fa-2x fa-chevron-down p-1"></i> + </div> + </Content> +</ScrollToPageBottom> + +<ScrollToPageTop> + <Content> + <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> + <i class="fas fa-2x fa-chevron-up p-1"></i> + </div> + </Content> +</ScrollToPageTop> + +<div class="container-fluid p-3 mb-3 border rounded"> <PermaLinkElement PermaLinkName="scroll-js" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> <Content><h3>Scroll JS</h3></Content> </PermaLinkElement> From 33f753f8a70904de161921b51ad434754d2f8e38 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sun, 28 Mar 2021 13:42:52 +0200 Subject: [PATCH 08/69] Implemented ScrollToPageBottom component. --- ...oft.Blazor.Components.Common.JsInterop.xml | 34 +++++++- .../Scroll/ScrollToPageBottom.razor | 81 +++++++++++++++---- .../Scroll/ScrollToPageBottom.razor.css | 7 ++ .../Scroll/ScrollToPageBottom.razor.min.css | 1 + .../Scroll/ScrollToPageTop.razor | 2 +- .../Scroll/ScrollToPageTop.razor.css | 1 + .../Scroll/ScrollToPageTop.razor.min.css | 2 +- .../bundleconfig.json | 6 ++ 8 files changed, 114 insertions(+), 20 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.css create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.min.css 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 94211681..c193d8a7 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 @@ -807,14 +807,44 @@ Scroll Y value </summary> </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.InnerElementReference"> + <summary> + Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + </summary> + </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.Content"> <summary> - + HTML Content of the scroll panel. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.VisibleFromPagePercentage"> + <summary> + Element should be visible when scroll reached page % of given value. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.VisibleUntilPagePercentage"> + <summary> + Element should be visible untill scroll reached page % of given value. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.SmootScroll"> <summary> - + Scroll shold be jump or smoothly scroll. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.PaddingFromTop"> + <summary> + Required space from page bottom in px. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.PaddingFromSide"> + <summary> + Required space from page (left/right) side in px. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.HorizontalOrientation"> + <summary> + Element orientation on page. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.InnerElementReference"> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor index 104b6643..6e2ada15 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor @@ -1,44 +1,93 @@ -@if (_isVisible) -{ - <div class="scrollToPageBottom" @onclick="async () => await _scrollHandler.ScrollToPageEndAsync(SmootScroll)"> - @Content - </div> -} +<div @ref="_inputRef" class="scrollToPageBottom @HorizontalOrientation.ToString().ToLower()" tabindex="500" + @onclick="async () => await _scrollHandler.ScrollToPageEndAsync(SmootScroll)"> + @Content +</div> <style> .scrollToPageBottom { - position: fixed; - top: 5rem; - right: 1.5rem; - z-index: 20; - cursor: pointer; + top: @(PaddingFromTop)px; + opacity: @(_isVisible ? 1 : 0); + } + .scrollToPageBottom.right { + right: @(PaddingFromSide)px; + } + .scrollToPageBottom.left { + left: @(PaddingFromSide)px; } </style> @implements IAsyncDisposable -@inject IScrollHandler _scrollHandler; -@code { - private bool _isVisible = true; +@inject IScrollHandler _scrollHandler +@inject ILogger<ScrollToPageBottom> _logger + +@code { + private bool _isVisible = false; protected override async Task OnInitializedAsync() { + WriteDiag("Component initialized..."); + await _scrollHandler.RegisterPageScrollAsync(async (e) => { + var scrollSize = await _scrollHandler.GetPageScrollSizeAsync(); + var percentage = (e.Y / scrollSize.Y) * 100; + + var visible = percentage >= VisibleFromPagePercentage && percentage <= VisibleUntilPagePercentage; + if (visible != _isVisible) + { + _isVisible = visible; + StateHasChanged(); + WriteDiag($"Component visibilit changed visible: {_isVisible}."); + } }); } + private ElementReference _inputRef; + /// <summary> + /// Exposes a Blazor <see cref="ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + /// </summary> + public ElementReference InnerElementReference => _inputRef; + /// <summary> - /// + /// HTML Content of the scroll panel. /// </summary> [Parameter] public RenderFragment Content { get; set; } /// <summary> - /// + /// Element should be visible when scroll reached page % of given value. + /// </summary> + [Parameter] public byte VisibleFromPagePercentage { get; set; } = 5; + /// <summary> + /// Element should be visible untill scroll reached page % of given value. + /// </summary> + [Parameter] public byte VisibleUntilPagePercentage { get; set; } = 80; + + /// <summary> + /// Scroll shold be jump or smoothly scroll. /// </summary> [Parameter] public bool SmootScroll { get; set; } = true; + /// <summary> + /// Required space from page bottom in px. + /// </summary> + [Parameter] public int PaddingFromTop { get; set; } = 24; + /// <summary> + /// Required space from page (left/right) side in px. + /// </summary> + [Parameter] public int PaddingFromSide { get; set; } = 24; + /// <summary> + /// Element orientation on page. + /// </summary> + [Parameter] public PageScrollHorizontalOrientation HorizontalOrientation { get; set; } = PageScrollHorizontalOrientation.Right; + + + private void WriteDiag(string message) + { + _logger.LogDebug($"Component {this.GetType()}: {message}"); + } + public async ValueTask DisposeAsync() { if (_scrollHandler is not null) diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.css b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.css new file mode 100644 index 00000000..ff2659d2 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.css @@ -0,0 +1,7 @@ +.scrollToPageBottom { + position: fixed; + z-index: 20; + cursor: pointer; + transition: opacity 0.25s linear; + outline: 0px; +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.min.css b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.min.css new file mode 100644 index 00000000..173d961d --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.min.css @@ -0,0 +1 @@ +.scrollToPageBottom{position:fixed;z-index:20;cursor:pointer;transition:opacity .25s linear;outline:0} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor index 3ad108ba..de09421a 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor @@ -1,4 +1,4 @@ -<div @ref="_inputRef" class="scrollToPageTop @HorizontalOrientation.ToString().ToLower()" tabindex="500" +<div @ref="_inputRef" class="scrollToPageTop @HorizontalOrientation.ToString().ToLower()" tabindex="501" @onclick="async () => await _scrollHandler.ScrollToPageTopAsync(SmootScroll)"> @Content </div> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css index ab507144..8545dc75 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css @@ -3,4 +3,5 @@ z-index: 20; cursor: pointer; transition: opacity 0.25s linear; + outline: 0px; } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css index dbcc64b4..20d81bac 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css @@ -1 +1 @@ -.scrollToPageTop{position:fixed;z-index:20;cursor:pointer;transition:opacity .25s linear} \ No newline at end of file +.scrollToPageTop{position:fixed;z-index:20;cursor:pointer;transition:opacity .25s linear;outline:0} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json b/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json index 48ca414e..bcb81836 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json @@ -64,5 +64,11 @@ "inputFiles": [ "Scroll/ScrollToPageTop.razor.css" ] + }, + { + "outputFileName": "Scroll/ScrollToPageBottom.razor.min.css", + "inputFiles": [ + "Scroll/ScrollToPageBottom.razor.css" + ] } ] From ec1b3cdc8fd6ae7daf8aa0a0c79b97a9de715d27 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sun, 28 Mar 2021 20:40:59 +0200 Subject: [PATCH 09/69] Scroll elements demo. --- ...oft.Blazor.Components.Common.JsInterop.xml | 14 ++--- ...ion.cs => PageScrollHorizontalPosition.cs} | 2 +- .../Scroll/ScrollToPageBottom.razor | 6 +- .../Scroll/ScrollToPageTop.razor | 6 +- .../Components/JSInterop.razor | 10 +-- .../Components/JsDemo/ScrollJs.razor | 61 ++++++++++++++++++- 6 files changed, 78 insertions(+), 21 deletions(-) rename src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/{PageScrollHorizontalOrientation.cs => PageScrollHorizontalPosition.cs} (89%) 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 c193d8a7..1dc1c367 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 @@ -757,17 +757,17 @@ PageScrollEventInfo event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback </summary> </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.PageScrollHorizontalOrientation"> + <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.PageScrollHorizontalPosition"> <summary> Page scroll elements orientation on page <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom"/> and <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop"/> elements </summary> </member> - <member name="F:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.PageScrollHorizontalOrientation.Right"> + <member name="F:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.PageScrollHorizontalPosition.Right"> <summary> Element placed to the Right side </summary> </member> - <member name="F:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.PageScrollHorizontalOrientation.Left"> + <member name="F:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.PageScrollHorizontalPosition.Left"> <summary> Element placed to the Left side </summary> @@ -842,9 +842,9 @@ Required space from page (left/right) side in px. </summary> </member> - <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.HorizontalOrientation"> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.HorizontalPosition"> <summary> - Element orientation on page. + Element position on page. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.InnerElementReference"> @@ -877,9 +877,9 @@ Required space from page (left/right) side in px. </summary> </member> - <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.HorizontalOrientation"> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.HorizontalPosition"> <summary> - Element orientation on page. + Element position on page. </summary> </member> </members> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/PageScrollHorizontalOrientation.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/PageScrollHorizontalPosition.cs similarity index 89% rename from src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/PageScrollHorizontalOrientation.cs rename to src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/PageScrollHorizontalPosition.cs index 0c62e6e0..602d3638 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/PageScrollHorizontalOrientation.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/PageScrollHorizontalPosition.cs @@ -3,7 +3,7 @@ /// <summary> /// Page scroll elements orientation on page <see cref="ScrollToPageBottom"/> and <see cref="ScrollToPageTop"/> elements /// </summary> - public enum PageScrollHorizontalOrientation + public enum PageScrollHorizontalPosition { /// <summary> /// Element placed to the Right side diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor index 6e2ada15..d7c24440 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor @@ -1,4 +1,4 @@ -<div @ref="_inputRef" class="scrollToPageBottom @HorizontalOrientation.ToString().ToLower()" tabindex="500" +<div @ref="_inputRef" class="scrollToPageBottom @HorizontalPosition.ToString().ToLower()" tabindex="500" @onclick="async () => await _scrollHandler.ScrollToPageEndAsync(SmootScroll)"> @Content </div> @@ -78,9 +78,9 @@ /// </summary> [Parameter] public int PaddingFromSide { get; set; } = 24; /// <summary> - /// Element orientation on page. + /// Element position on page. /// </summary> - [Parameter] public PageScrollHorizontalOrientation HorizontalOrientation { get; set; } = PageScrollHorizontalOrientation.Right; + [Parameter] public PageScrollHorizontalPosition HorizontalPosition { get; set; } = PageScrollHorizontalPosition.Right; private void WriteDiag(string message) diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor index de09421a..d5cea1e0 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor @@ -1,4 +1,4 @@ -<div @ref="_inputRef" class="scrollToPageTop @HorizontalOrientation.ToString().ToLower()" tabindex="501" +<div @ref="_inputRef" class="scrollToPageTop @HorizontalPosition.ToString().ToLower()" tabindex="501" @onclick="async () => await _scrollHandler.ScrollToPageTopAsync(SmootScroll)"> @Content </div> @@ -74,9 +74,9 @@ /// </summary> [Parameter] public int PaddingFromSide { get; set; } = 24; /// <summary> - /// Element orientation on page. + /// Element position on page. /// </summary> - [Parameter] public PageScrollHorizontalOrientation HorizontalOrientation { get; set; } = PageScrollHorizontalOrientation.Right; + [Parameter] public PageScrollHorizontalPosition HorizontalPosition { get; set; } = PageScrollHorizontalPosition.Right; private void WriteDiag(string message) diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor index f357bc21..7247062a 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor @@ -16,9 +16,9 @@ <hr /> @*Scroll to page end with ScrollHandler for custom floating component see ScrollToPageBottom*@ -<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageEndAsync(_smmothScroll))">Scroll to Page bottom <i class="fas fa-arrow-down"></i></button> +<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageEndAsync(_smoothScroll))">Scroll to Page bottom <i class="fas fa-arrow-down"></i></button> Smooth scroll: -<input type="checkbox" @bind="_smmothScroll" /> +<input type="checkbox" @bind="_smoothScroll" /> <div> <h3>JS Interop features:</h3> @@ -58,9 +58,9 @@ Smooth scroll: <hr /> @*Scroll to page end with ScrollHandler for custom floating component see ScrollToPageTop*@ -<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageTopAsync(_smmothScroll))">Scroll to Page top <i class="fas fa-arrow-up"></i></button> +<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageTopAsync(_smoothScroll))">Scroll to Page top <i class="fas fa-arrow-up"></i></button> Smooth scroll: -<input type="checkbox" @bind="_smmothScroll" /> +<input type="checkbox" @bind="_smoothScroll" /> @implements IAsyncDisposable @inject IScrollHandler _scrollHandler @@ -74,5 +74,5 @@ Smooth scroll: } } - private bool _smmothScroll = true; + private bool _smoothScroll = true; } \ No newline at end of file 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 c2f567dd..b43f8993 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor @@ -1,5 +1,10 @@ @*Scroll on page components, can be placed anywhere on a page but should be used one per page*@ -<ScrollToPageBottom> +<ScrollToPageBottom PaddingFromTop="@_scrcollTopBottonMargin" + PaddingFromSide="@_scrcollTopSideMargin" + HorizontalPosition="@_scrollHorizontalPosition" + SmootScroll="@_smoothScroll" + VisibleFromPagePercentage="5" + VisibleUntilPagePercentage="70"> <Content> <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> <i class="fas fa-2x fa-chevron-down p-1"></i> @@ -7,7 +12,11 @@ </Content> </ScrollToPageBottom> -<ScrollToPageTop> +<ScrollToPageTop PaddingFromBottom="@_scrcollTopBottonMargin" + PaddingFromSide="@_scrcollTopSideMargin" + HorizontalPosition="@_scrollHorizontalPosition" + SmootScroll="@_smoothScroll" + VisibleFromPagePercentage="30"> <Content> <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> <i class="fas fa-2x fa-chevron-up p-1"></i> @@ -34,6 +43,47 @@ <hr /> + <p> + <label> + <strong><code>ScrollToPageBottom</code> and <code>ScrollToPageTop</code></strong> components will render "floating" element with customizable placing and content + for wrapping <strong>Scroll JS</strong> scroll to page top or bottom functions. + <strong>Both component can be set inside ANY components which will apply for the whole page. Hence both components should be added only once per page!</strong> + </label> + </p> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Top/Bottom margin (can be set separately for both types of controls): @(_scrcollTopBottonMargin)px + <input type="range" class="w-100" min="0" max="100" @bind="_scrcollTopBottonMargin" @oninput="(e => _scrcollTopBottonMargin = int.Parse(e.Value?.ToString()))" /> + </div> + </div> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Side right/left margin (can be set separately for both types of controls): @(_scrcollTopSideMargin)px + <input type="range" class="w-100" min="0" max="100" @bind="_scrcollTopSideMargin" @oninput="(e => _scrcollTopSideMargin = int.Parse(e.Value?.ToString()))" /> + </div> + </div> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Smooth scroll (can be set separately for both types of controls): <input type="checkbox" @bind="_smoothScroll" /> + </div> + </div> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Horizontal Position right/left (can be set separately for both types of controls): <select class="form-control selectpicker w-100" @bind="_scrollHorizontalPosition"> + @foreach (var item in Enum.GetValues(typeof(PageScrollHorizontalPosition))) + { + <option value="@item">@item</option> + } + </select> + </div> + </div> + + <hr /> + <div class="row pb-2"> <div class="col-12"> <p><code>RegisterPageScrollAsync()</code> will add event listener to HTML document/window <code>scroll</code></p> @@ -101,6 +151,13 @@ _scrollSubscribed = !_scrollSubscribed; } + //Page scroll elements + private int _scrcollTopBottonMargin = 20; + private int _scrcollTopSideMargin = 24; + private byte _scrcollBefore = 5; + private PageScrollHorizontalPosition _scrollHorizontalPosition = PageScrollHorizontalPosition.Right; + private bool _smoothScroll = true; + public async ValueTask DisposeAsync() { if (_scrollHandler is not null) From 09ae2a9ef34d85515d0dd5ee09575de0fdcdace7 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sun, 28 Mar 2021 21:17:32 +0200 Subject: [PATCH 10/69] Scroll elements docs. --- .github/docs/JsInterop.md | 54 ++++++++++++++++++- ...oft.Blazor.Components.Common.JsInterop.xml | 10 ++-- .../Scroll/ScrollToPageBottom.razor | 6 +-- .../Scroll/ScrollToPageTop.razor | 4 +- .../Components/JsDemo/ScrollJs.razor | 4 +- 5 files changed, 65 insertions(+), 13 deletions(-) diff --git a/.github/docs/JsInterop.md b/.github/docs/JsInterop.md index efc941b1..22a95cfa 100644 --- a/.github/docs/JsInterop.md +++ b/.github/docs/JsInterop.md @@ -21,7 +21,8 @@ You can try it out by using the [demo app](https://blazorextensions.z6.web.core. - **Global Mouse JS**: is an **injectable `IGlobalMouseEventHandler` service** for global mouse callback event handlers. - **Focus JS**: is an injectable `IFocusHandler` service. **Focus JS is able to identify and restore focus on ANY DOM element without using Blazor `@ref=""` tag.** - **Element info JS**: is a set of **Extension methods** for `ElementReference` objects. -- **Scroll JS**: is a set of **Extension methods** for `ElementReference` objects. Also an **injectable `IScrollHandler` service** for non element level functions and callback event handlers. +- **Scroll JS**: is a set of **Extension methods** for `ElementReference` objects. **`IScrollHandler` injectable service** for non element level functions and callback event handlers. + Also `ScrollToPageBottom` and `ScrollToPageTop` components will render "floating" element with customizable placing and content for wrapping Scroll JS scroll to page top or bottom functions. - **Resize JS**: is an **injectable `IResizeHandler` service** for Window (global) and HTML Elements resize event callback handlers. - **Clipboard JS**: is an **injectable `IClipboardHandler` service** for accessing computer Clipboard from Blazor Application. - **Language JS**: is an **injectable `ILanguageService` service** for detect the browser language preference. @@ -108,6 +109,57 @@ Returns the given HTML element ClintBoundRect data as `DomRect`. ## Scroll JS (See: [demo app](https://blazorextensions.z6.web.core.windows.net/jsinterop#scroll-js)) **Scroll JS** is a set of **Extension methods** for `ElementReference` objects. Also an **injectable `IScrollHandler` service** for non element level functions and callback event handlers. +`ScrollToPageBottom` and `ScrollToPageTop` components will render "floating" element with customizable placing and content for wrapping `Scroll JS` scroll to page top or bottom functions. +**Note**: Both component can be set inside ANY components which will apply for the whole page. Hence both components should be added only once per page! + +### `ScrollToPageBottom` component +`ScrollToPageBottom` component will render "floating" element with customizable placing and content for wrapping Scroll JS **scroll to page bottom** function. + +#### Properties +- **`Content`: `RenderFragment` HTML content - Required** <br /> +Required HTML content which will be wrapped into a `<span>` which has the Click events listener registered. +- **`VisibleFromPagePercentage`: `byte { get; set; }` (default: 5)** <br /> +Element should be visible when scroll reached page % of given value. +- **`VisibleUntilPagePercentage`: `byte { get; set; }` (default: 80)** <br /> +Element should be visible until scroll reached page % of given value. +- **`SmootScroll`: `bool { get; set; }` (default: true)** <br /> +Scroll should be jump or smoothly scroll. +- **`PaddingFromTop`: `int { get; set; }` (default: 24)** <br /> +Required space from page bottom in px. +- **`PaddingFromSide`: `int { get; set; }` (default: 24)** <br /> +Required space from page (left/right) side in px. +- **`HorizontalPosition`: `PageScrollHorizontalPosition { get; set; }` (default: 24)** <br /> +Element position on page {Right, Left}. +- **`InnerElementReference`: `ElementReference { get; }`** <br /> +Exposes a Blazor `ElementReference` of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + +#### Functions +- **`DisposeAsync()`: `ValueTask IAsyncDisposable()` interface** <br /> +Component implements `IAsyncDisposable` interface Blazor framework components also can `@implements IAsyncDisposable` where the injected service should be Disposed. + +### `ScrollToPageTop` component +`ScrollToPageTop` component will render "floating" element with customizable placing and content for wrapping Scroll JS **scroll to page top** function. + +#### Properties +- **`Content`: `RenderFragment` HTML content - Required** <br /> +Required HTML content which will be wrapped into a `<span>` which has the Click events listener registered. +- **`VisibleFromPagePercentage`: `byte { get; set; }` (default: 30)** <br /> +Element should be visible when scroll reached page % of given value. +- **`SmootScroll`: `bool { get; set; }` (default: true)** <br /> +Scroll should be jump or smoothly scroll. +- **`PaddingFromTop`: `int { get; set; }` (default: 24)** <br /> +Required space from page bottom in px. +- **`PaddingFromSide`: `int { get; set; }` (default: 24)** <br /> +Required space from page (left/right) side in px. +- **`HorizontalPosition`: `PageScrollHorizontalPosition { get; set; }` (default: 24)** <br /> +Element position on page {Right, Left}. +- **`InnerElementReference`: `ElementReference { get; }`** <br /> +Exposes a Blazor `ElementReference` of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + +#### Functions +- **`DisposeAsync()`: `ValueTask IAsyncDisposable()` interface** <br /> +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)`**<br /> 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 1dc1c367..645f1617 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 @@ -824,12 +824,12 @@ </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.VisibleUntilPagePercentage"> <summary> - Element should be visible untill scroll reached page % of given value. + Element should be visible until scroll reached page % of given value. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.SmootScroll"> <summary> - Scroll shold be jump or smoothly scroll. + Scroll should be jump or smoothly scroll. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.PaddingFromTop"> @@ -844,7 +844,7 @@ </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.HorizontalPosition"> <summary> - Element position on page. + Element position on page {Right, Left}. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.InnerElementReference"> @@ -864,7 +864,7 @@ </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.SmootScroll"> <summary> - Scroll shold be jump or smoothly scroll. + Scroll should be jump or smoothly scroll. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.PaddingFromBottom"> @@ -879,7 +879,7 @@ </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.HorizontalPosition"> <summary> - Element position on page. + Element position on page {Right, Left}. </summary> </member> </members> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor index d7c24440..dac749f0 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor @@ -60,12 +60,12 @@ /// </summary> [Parameter] public byte VisibleFromPagePercentage { get; set; } = 5; /// <summary> - /// Element should be visible untill scroll reached page % of given value. + /// Element should be visible until scroll reached page % of given value. /// </summary> [Parameter] public byte VisibleUntilPagePercentage { get; set; } = 80; /// <summary> - /// Scroll shold be jump or smoothly scroll. + /// Scroll should be jump or smoothly scroll. /// </summary> [Parameter] public bool SmootScroll { get; set; } = true; @@ -78,7 +78,7 @@ /// </summary> [Parameter] public int PaddingFromSide { get; set; } = 24; /// <summary> - /// Element position on page. + /// Element position on page {Right, Left}. /// </summary> [Parameter] public PageScrollHorizontalPosition HorizontalPosition { get; set; } = PageScrollHorizontalPosition.Right; diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor index d5cea1e0..55a4bc6e 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor @@ -61,7 +61,7 @@ [Parameter] public byte VisibleFromPagePercentage { get; set; } = 30; /// <summary> - /// Scroll shold be jump or smoothly scroll. + /// Scroll should be jump or smoothly scroll. /// </summary> [Parameter] public bool SmootScroll { get; set; } = true; @@ -74,7 +74,7 @@ /// </summary> [Parameter] public int PaddingFromSide { get; set; } = 24; /// <summary> - /// Element position on page. + /// Element position on page {Right, Left}. /// </summary> [Parameter] public PageScrollHorizontalPosition HorizontalPosition { get; set; } = PageScrollHorizontalPosition.Right; 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 b43f8993..d2bf7019 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor @@ -3,8 +3,8 @@ PaddingFromSide="@_scrcollTopSideMargin" HorizontalPosition="@_scrollHorizontalPosition" SmootScroll="@_smoothScroll" - VisibleFromPagePercentage="5" - VisibleUntilPagePercentage="70"> + VisibleFromPagePercentage="2" + VisibleUntilPagePercentage="75"> <Content> <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> <i class="fas fa-2x fa-chevron-down p-1"></i> From c28f40133b02583c0606b33c83645f34ae0289c5 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 29 Mar 2021 10:06:50 +0200 Subject: [PATCH 11/69] JS docs updates. --- .github/docs/JsInterop.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/docs/JsInterop.md b/.github/docs/JsInterop.md index 22a95cfa..d86aa0d2 100644 --- a/.github/docs/JsInterop.md +++ b/.github/docs/JsInterop.md @@ -16,13 +16,16 @@ You can try it out by using the [demo app](https://blazorextensions.z6.web.core. # 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. +- **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. - **Global Mouse JS**: is an **injectable `IGlobalMouseEventHandler` service** for global mouse callback event handlers. - **Focus JS**: is an injectable `IFocusHandler` service. **Focus JS is able to identify and restore focus on ANY DOM element without using Blazor `@ref=""` tag.** - **Element info JS**: is a set of **Extension methods** for `ElementReference` objects. -- **Scroll JS**: is a set of **Extension methods** for `ElementReference` objects. **`IScrollHandler` injectable service** for non element level functions and callback event handlers. - Also `ScrollToPageBottom` and `ScrollToPageTop` components will render "floating" element with customizable placing and content for wrapping Scroll JS scroll to page top or bottom functions. +- **Scroll JS**: + - Set of **Extension methods** for `ElementReference` objects. + - **`IScrollHandler` injectable service** for non element level functions and callback event handlers. + - Also `ScrollToPageBottom` and `ScrollToPageTop` components will render "floating" element with customizable placing and content for wrapping Scroll JS scroll to page top or bottom functions. - **Resize JS**: is an **injectable `IResizeHandler` service** for Window (global) and HTML Elements resize event callback handlers. - **Clipboard JS**: is an **injectable `IClipboardHandler` service** for accessing computer Clipboard from Blazor Application. - **Language JS**: is an **injectable `ILanguageService` service** for detect the browser language preference. From 1b78b7615e489a0c5d51396289a4b5138301e218 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 29 Mar 2021 11:40:45 +0200 Subject: [PATCH 12/69] Scroll on page components issue fixes with opacity. --- .../Scroll/ScrollToPageBottom.razor | 2 +- .../Scroll/ScrollToPageBottom.razor.css | 5 +++++ .../Scroll/ScrollToPageBottom.razor.min.css | 2 +- .../Scroll/ScrollToPageTop.razor | 2 +- .../Scroll/ScrollToPageTop.razor.css | 5 +++++ .../Scroll/ScrollToPageTop.razor.min.css | 2 +- .../Components/JsDemo/ScrollJs.razor | 2 +- 7 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor index dac749f0..4d32e6a7 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor @@ -1,4 +1,4 @@ -<div @ref="_inputRef" class="scrollToPageBottom @HorizontalPosition.ToString().ToLower()" tabindex="500" +<div @ref="_inputRef" class="scrollToPageBottom @HorizontalPosition.ToString().ToLower() @(_isVisible ? "" : "hidden")" tabindex="500" @onclick="async () => await _scrollHandler.ScrollToPageEndAsync(SmootScroll)"> @Content </div> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.css b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.css index ff2659d2..312d1bbc 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.css +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.css @@ -4,4 +4,9 @@ cursor: pointer; transition: opacity 0.25s linear; outline: 0px; +} + +.scrollToPageBottom.hidden { + cursor: pointer; + pointer-events: none; } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.min.css b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.min.css index 173d961d..356aa571 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.min.css +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor.min.css @@ -1 +1 @@ -.scrollToPageBottom{position:fixed;z-index:20;cursor:pointer;transition:opacity .25s linear;outline:0} \ No newline at end of file +.scrollToPageBottom{position:fixed;z-index:20;cursor:pointer;transition:opacity .25s linear;outline:0}.scrollToPageBottom.hidden{cursor:pointer;pointer-events:none} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor index 55a4bc6e..be9ba0cf 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor @@ -1,4 +1,4 @@ -<div @ref="_inputRef" class="scrollToPageTop @HorizontalPosition.ToString().ToLower()" tabindex="501" +<div @ref="_inputRef" class="scrollToPageTop @HorizontalPosition.ToString().ToLower() @(_isVisible ? "" : "hidden")" tabindex="501" @onclick="async () => await _scrollHandler.ScrollToPageTopAsync(SmootScroll)"> @Content </div> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css index 8545dc75..d4abf22c 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.css @@ -4,4 +4,9 @@ cursor: pointer; transition: opacity 0.25s linear; outline: 0px; +} + +.scrollToPageTop.hidden { + cursor: pointer; + pointer-events: none; } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css index 20d81bac..db204003 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor.min.css @@ -1 +1 @@ -.scrollToPageTop{position:fixed;z-index:20;cursor:pointer;transition:opacity .25s linear;outline:0} \ No newline at end of file +.scrollToPageTop{position:fixed;z-index:20;cursor:pointer;transition:opacity .25s linear;outline:0}.scrollToPageTop.hidden{cursor:pointer;pointer-events:none} \ No newline at end of file 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 d2bf7019..c0e68995 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor @@ -1,4 +1,4 @@ -@*Scroll on page components, can be placed anywhere on a page but should be used one per page*@ +@*Scroll on page components, can be placed anywhere on a page, moved to a new component, etc. but should be used once per page*@ <ScrollToPageBottom PaddingFromTop="@_scrcollTopBottonMargin" PaddingFromSide="@_scrcollTopSideMargin" HorizontalPosition="@_scrollHorizontalPosition" From 93c94d3d47a340ff97c07c8a6c5c5d2ee7949740 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 29 Mar 2021 11:57:12 +0200 Subject: [PATCH 13/69] Update demo app with page scroll --- .../Components/BrowserStorage.razor | 4 +++- .../Components/Collapse.razor | 4 +++- .../Components/CssEvents.razor | 2 ++ .../Components/Debounce.razor | 4 +++- .../Components/Dialog.razor | 4 +++- .../Components/Index.razor | 4 +++- .../Components/JSInterop.razor | 4 ++-- .../Components/Loading.razor | 1 + .../Components/Maps.razor | 2 ++ .../Components/Permalink.razor | 1 + .../Components/Tabs.razor | 4 +++- .../Components/Toggle.razor | 4 +++- .../Components/Typeahead.razor | 1 + .../Shared/PageScroll.razor | 16 ++++++++++++++++ 14 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/BrowserStorage.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/BrowserStorage.razor index 4e214371..867aeb57 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/BrowserStorage.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/BrowserStorage.razor @@ -1,4 +1,6 @@ -<div class="container-fluid p-3 mb-3 border rounded"> +<PageScroll /> + +<div class="container-fluid p-3 mb-3 border rounded"> <h1>Browser Storage extensions</h1> <p> Enables <a href="https://developer.mozilla.org/en-US/docs/Web/API/Storage" target="_blank">Browser Local and Session</a> storages and diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Collapse.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Collapse.razor index e53a281e..66e7db01 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Collapse.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Collapse.razor @@ -1,4 +1,6 @@ -<h1>Collapse Components</h1> +<PageScroll /> + +<h1>Collapse Components</h1> <p> Blazor component that renders customizable Collapsible/Expandable panel and Accordion with many but only one active panel also custom content and header. For usege see soruce code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Collapse.md" target="_blank">Github</a>. diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/CssEvents.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/CssEvents.razor index e734e8b1..4dccb7af 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/CssEvents.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/CssEvents.razor @@ -12,6 +12,8 @@ <li><NavLink href="cssevents#animation">Animations</NavLink></li> </ul> +<PageScroll /> + <CssEventsTransition /> <CssEventsAnimation /> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Debounce.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Debounce.razor index b78c3384..911fef99 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Debounce.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Debounce.razor @@ -1,4 +1,6 @@ -<h1>Deboudnce Input controls</h1> +<PageScroll /> + +<h1>Deboudnce Input controls</h1> <p> Blazor component that renders an Input, InputText, Textarea or InputTextarea, etc. element with debounced onChange. For usege see soruce code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/DebounceInputs.md" target="_blank">Github</a>. diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor index d19078d4..1a21b5b9 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor @@ -1,4 +1,6 @@ -<h1>Modal Component</h1> +<PageScroll /> + +<h1>Modal Component</h1> <p> Blazor component that can be used to render Modal dialog window with customizable content and parameterized Overlay, etc.. For usege see soruce code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Modal.md" target="_blank">Github</a>. diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor index 28374820..3aaeb98c 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor @@ -1,4 +1,6 @@ -<h1>ASP.NET Blazor Components</h1> +<PageScroll /> + +<h1>ASP.NET Blazor Components</h1> <p> <strong>Blazor Components</strong> is a set of <strong>UI Components</strong> and other useful <strong>Extensions</strong> for <a href="https://blazor.net/" target="_blank">Blazor</a> applications. diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor index 7247062a..0434ef8c 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor @@ -16,7 +16,7 @@ <hr /> @*Scroll to page end with ScrollHandler for custom floating component see ScrollToPageBottom*@ -<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageEndAsync(_smoothScroll))">Scroll to Page bottom <i class="fas fa-arrow-down"></i></button> +<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageEndAsync(_smoothScroll))">Scroll to Page bottom <i class="fas fa-arrow-down"></i> (See floating components)</button> Smooth scroll: <input type="checkbox" @bind="_smoothScroll" /> @@ -58,7 +58,7 @@ Smooth scroll: <hr /> @*Scroll to page end with ScrollHandler for custom floating component see ScrollToPageTop*@ -<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageTopAsync(_smoothScroll))">Scroll to Page top <i class="fas fa-arrow-up"></i></button> +<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageTopAsync(_smoothScroll))">Scroll to Page top <i class="fas fa-arrow-up"></i> (See floating components)</button> Smooth scroll: <input type="checkbox" @bind="_smoothScroll" /> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor index 3751c083..3151049c 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor @@ -1,4 +1,5 @@ @using System.ComponentModel.DataAnnotations; +<PageScroll /> <h1>Loading Components</h1> <p> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Maps.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Maps.razor index f1c14b66..8e487a44 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Maps.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Maps.razor @@ -20,6 +20,8 @@ </ul> </div> +<PageScroll /> + <MapsGoogle /> <MapsBing /> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor index 9d9988bf..9ed6acb3 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor @@ -3,6 +3,7 @@ textarea { height: 150px !important; } </style> +<PageScroll /> <h1>Permalink (#link) extension and component</h1> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor index 223f02aa..4fa8ad56 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor @@ -1,4 +1,6 @@ -<h1>Tabs Components</h1> +<PageScroll /> + +<h1>Tabs Components</h1> <p> Blazor component that renders customizable Tabs element panel with many tabs and custom content. For usege see soruce code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Tabs.md" target="_blank">Github</a>. diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Toggle.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Toggle.razor index 5522c9c4..1adcd984 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Toggle.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Toggle.razor @@ -1,4 +1,6 @@ -<h1>Toggler Components</h1> +<PageScroll /> + +<h1>Toggler Components</h1> <p> Blazor component that renders customizable Toggle Switch and Toggle Button components. For usege see soruce code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Toggle.md" target="_blank">Github</a>. diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Typeahead.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Typeahead.razor index b17480d4..a1b87cfa 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Typeahead.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Typeahead.razor @@ -3,6 +3,7 @@ textarea { height: 150px !important; } </style> +<PageScroll /> <h1>Typeahead Input controls</h1> <p> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor new file mode 100644 index 00000000..3cdd2ae6 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor @@ -0,0 +1,16 @@ +@*Scroll on page components wrapped into it's own component and applied on some demo pages*@ +<ScrollToPageBottom VisibleFromPagePercentage="2" VisibleUntilPagePercentage="75"> + <Content> + <div style="background:whitesmoke; border-radius:16px; width: 32px; height:32px; box-shadow: 1px 2px #e1dddd;"> + <i style="padding-left: 6px; padding-top:2px;" class="fas fa-2x fa-angle-down"></i> + </div> + </Content> +</ScrollToPageBottom> + +<ScrollToPageTop VisibleFromPagePercentage="20"> + <Content> + <div style="background:whitesmoke; border-radius:16px; width: 32px; height:32px; box-shadow: 1px 2px #e1dddd;"> + <i style="padding-left: 6px; padding-bottom:2px;" class="fas fa-2x fa-angle-up"></i> + </div> + </Content> +</ScrollToPageTop> \ No newline at end of file From 3b9756e681acfdcc45f3d9a89f5bda13ce12a45d Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sun, 4 Apr 2021 19:04:49 +0200 Subject: [PATCH 14/69] Created Majorsoft.Blazor.Extensions.Analytics project --- src/Majorsoft.Blazor.Components.sln | 7 +++ .../ExampleJsInterop.cs | 40 ++++++++++++++ ...jorsoft.Blazor.Extensions.Analytics.csproj | 54 +++++++++++++++++++ .../Majorsoft.Blazor.Extensions.Analytics.xml | 8 +++ .../_Imports.razor | 1 + .../bundleconfig.json | 8 +++ .../wwwroot/googleAnalytics.js | 3 ++ .../wwwroot/googleAnalytics.min.js | 1 + 8 files changed, 122 insertions(+) create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/ExampleJsInterop.cs create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.csproj create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/_Imports.razor create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/bundleconfig.json create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js diff --git a/src/Majorsoft.Blazor.Components.sln b/src/Majorsoft.Blazor.Components.sln index 715272e3..7f29ea39 100644 --- a/src/Majorsoft.Blazor.Components.sln +++ b/src/Majorsoft.Blazor.Components.sln @@ -85,6 +85,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Pipelines", "_Pipelines", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Extensions.BrowserStorage", "Majorsoft.Blazor.Extensions.BrowserStorage\Majorsoft.Blazor.Extensions.BrowserStorage.csproj", "{B0BDDF05-5508-4D05-B766-6DB53C33D004}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Extensions.Analytics", "Majorsoft.Blazor.Extensions.Analytics\Majorsoft.Blazor.Extensions.Analytics.csproj", "{261B879A-4E63-4D4B-9B59-E8C8F84531FF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -215,6 +217,10 @@ Global {B0BDDF05-5508-4D05-B766-6DB53C33D004}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0BDDF05-5508-4D05-B766-6DB53C33D004}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0BDDF05-5508-4D05-B766-6DB53C33D004}.Release|Any CPU.Build.0 = Release|Any CPU + {261B879A-4E63-4D4B-9B59-E8C8F84531FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {261B879A-4E63-4D4B-9B59-E8C8F84531FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {261B879A-4E63-4D4B-9B59-E8C8F84531FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {261B879A-4E63-4D4B-9B59-E8C8F84531FF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -250,6 +256,7 @@ Global {3AD2A154-A197-46BE-A213-51328C13B008} = {B6905E0B-759A-4928-B38A-9210B5CC8988} {CE8AF2E1-6860-4F1B-BC96-F84042E55A2F} = {020CAF9C-D289-470A-BB97-E58D73DDCE70} {B0BDDF05-5508-4D05-B766-6DB53C33D004} = {121A4471-EF7A-4F9A-A856-67E8376A4AD2} + {261B879A-4E63-4D4B-9B59-E8C8F84531FF} = {121A4471-EF7A-4F9A-A856-67E8376A4AD2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F3E8B7F7-3F7C-4D6C-8B7B-48F1AF4694B9} diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/ExampleJsInterop.cs b/src/Majorsoft.Blazor.Extensions.Analytics/ExampleJsInterop.cs new file mode 100644 index 00000000..807334e7 --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/ExampleJsInterop.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; + +using Microsoft.JSInterop; + +namespace Majorsoft.Blazor.Extensions.Analytics +{ + // This class provides an example of how JavaScript functionality can be wrapped + // in a .NET class for easy consumption. The associated JavaScript module is + // loaded on demand when first needed. + // + // This class can be registered as scoped DI service and then injected into Blazor + // components for use. + + public class ExampleJsInterop : IAsyncDisposable + { + private readonly Lazy<Task<IJSObjectReference>> moduleTask; + + public ExampleJsInterop(IJSRuntime jsRuntime) + { + moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>( + "import", "./_content/Majorsoft.Blazor.Analytics/exampleJsInterop.js").AsTask()); + } + + public async ValueTask<string> Prompt(string message) + { + var module = await moduleTask.Value; + return await module.InvokeAsync<string>("showPrompt", message); + } + + public async ValueTask DisposeAsync() + { + if (moduleTask.IsValueCreated) + { + var module = await moduleTask.Value; + await module.DisposeAsync(); + } + } + } +} diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.csproj b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.csproj new file mode 100644 index 00000000..2fc77c1e --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.csproj @@ -0,0 +1,54 @@ +<Project Sdk="Microsoft.NET.Sdk.Razor"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + <nullable>enable</nullable> + <Version>1.0.0.0</Version> + <GeneratePackageOnBuild>true</GeneratePackageOnBuild> + <PackageIcon>blazor.components.png</PackageIcon> + <PackageId>Majorsoft.Blazor.Extensions.Analytics</PackageId> + <Authors>Imre Toth</Authors> + <Company>Majorsoft</Company> + <Product>Blazor Components</Product> + <Copyright>©2021 Imre Toth</Copyright> + <PackageLicenseExpression></PackageLicenseExpression> + <PackageProjectUrl>https://github.com/majorimi/blazor-components/blob/master/.github/docs/BrowserStorage.md</PackageProjectUrl> + <RepositoryUrl>https://github.com/majorimi/blazor-components</RepositoryUrl> + <RepositoryType>Git</RepositoryType> + <PackageTags>.Net5 Blazor Analytics Google Tracking Statistics</PackageTags> + <PackageReleaseNotes>See Releases here: https://github.com/majorimi/blazor-components/releases</PackageReleaseNotes> + <Description>Blazor extension that enables WebAssemply site analytics e.g. Google, etc. Part of Majorsoft Blazor library.</Description> + <Title>Analytics Google Tracking</Title> + <PackageLicenseFile>License.txt</PackageLicenseFile> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <DocumentationFile>.\Majorsoft.Blazor.Extensions.Analytics.xml</DocumentationFile> + </PropertyGroup> + + <ItemGroup> + <Content Remove="bundleconfig.json" /> + </ItemGroup> + + <ItemGroup> + <None Include="..\..\.github\Images\blazor.components.png"> + <Pack>True</Pack> + <PackagePath></PackagePath> + </None> + <None Include="..\..\.github\License.txt"> + <Pack>True</Pack> + <PackagePath></PackagePath> + </None> + <None Include="bundleconfig.json" /> + </ItemGroup> + + + <ItemGroup> + <SupportedPlatform Include="browser" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.3" /> + </ItemGroup> + +</Project> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml new file mode 100644 index 00000000..10b14e47 --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<doc> + <assembly> + <name>Majorsoft.Blazor.Extensions.Analytics</name> + </assembly> + <members> + </members> +</doc> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/_Imports.razor b/src/Majorsoft.Blazor.Extensions.Analytics/_Imports.razor new file mode 100644 index 00000000..77285129 --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/_Imports.razor @@ -0,0 +1 @@ +@using Microsoft.AspNetCore.Components.Web diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/bundleconfig.json b/src/Majorsoft.Blazor.Extensions.Analytics/bundleconfig.json new file mode 100644 index 00000000..991f9ed5 --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/bundleconfig.json @@ -0,0 +1,8 @@ +[ + { + "outputFileName": "wwwroot/googleAnalytics.min.js", + "inputFiles": [ + "wwwroot/googleAnalytics.js" + ] + } +] diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js new file mode 100644 index 00000000..4c9cb888 --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js @@ -0,0 +1,3 @@ +export function showPrompt(message) { + return prompt(message, 'Type anything here'); +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js new file mode 100644 index 00000000..d88e4114 --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js @@ -0,0 +1 @@ +export function showPrompt(n){return prompt(n,"Type anything here")} \ No newline at end of file From 4afa519341d0d06fc675ebd1158ef598a89fc033 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sun, 4 Apr 2021 21:06:33 +0200 Subject: [PATCH 15/69] Created analytics menu and demo page. --- .../Pages/AnalyticsPage.razor | 3 +++ .../Components/GoogleAnalytics.razor | 21 +++++++++++++++++++ .../Pages/AnalyticsPage.razor | 3 +++ .../Shared/NavMenu.razor | 5 ++++- .../Pages/AnalyticsPage.razor | 3 +++ 5 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 src/Majorsoft.Blazor.Components.TestApp/Pages/AnalyticsPage.razor create mode 100644 src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor create mode 100644 src/Majorsoft.Blazor.Components.TestApps.Common/Pages/AnalyticsPage.razor create mode 100644 src/Majorsoft.Blazor.Components.TestServerApp/Pages/AnalyticsPage.razor diff --git a/src/Majorsoft.Blazor.Components.TestApp/Pages/AnalyticsPage.razor b/src/Majorsoft.Blazor.Components.TestApp/Pages/AnalyticsPage.razor new file mode 100644 index 00000000..9850231d --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApp/Pages/AnalyticsPage.razor @@ -0,0 +1,3 @@ +@page "/analytics" + +<GoogleAnalytics /> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor new file mode 100644 index 00000000..68590c80 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor @@ -0,0 +1,21 @@ +<PageScroll /> + +<div class="container-fluid p-3 mb-3 border rounded"> + <h1>Website Analytics extensions</h1> + <p> + Blazor extension that enables WebAssemply site analytics e.g. Google, etc. For usage see source code and docs on + <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Analytics.md" target="_blank">Github</a>. + <br /><strong>Majorsoft.Blazor.Extensions.Analytics</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Extensions.Analytics" target="_blank">Nuget</a> + </p> + + <div> + <h3>Browser Storage features:</h3> + <ul> + <li><NavLink href="analytics#google">Google Analytics</NavLink></li> + </ul> + </div> +</div> + +@code { + +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/AnalyticsPage.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/AnalyticsPage.razor new file mode 100644 index 00000000..9850231d --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/AnalyticsPage.razor @@ -0,0 +1,3 @@ +@page "/analytics" + +<GoogleAnalytics /> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor index 018c8127..729bcfce 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor @@ -24,7 +24,10 @@ <NavLink class="nav-link" href="logger">Console logger</NavLink> </li> <li class="nav-item"> - <NavLink class="nav-link" href="browserstorage">Browser strorage</NavLink> + <NavLink class="nav-link" href="browserstorage">Browser storage</NavLink> + </li> + <li class="nav-item"> + <NavLink class="nav-link" href="analytics">Analytics</NavLink> </li> </ul> </div> diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Pages/AnalyticsPage.razor b/src/Majorsoft.Blazor.Components.TestServerApp/Pages/AnalyticsPage.razor new file mode 100644 index 00000000..9850231d --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Pages/AnalyticsPage.razor @@ -0,0 +1,3 @@ +@page "/analytics" + +<GoogleAnalytics /> \ No newline at end of file From a81b6a95bca5fc6d0c1d69aae4d99e11ab097586 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Tue, 6 Apr 2021 21:31:25 +0200 Subject: [PATCH 16/69] Google analytics service. --- .../Components/GoogleAnalytics.razor | 15 ++++++- .../ExampleJsInterop.cs | 40 ----------------- .../Google/GoogleAnalyticsService.cs | 44 +++++++++++++++++++ .../Majorsoft.Blazor.Extensions.Analytics.xml | 10 +++++ 4 files changed, 68 insertions(+), 41 deletions(-) delete mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/ExampleJsInterop.cs create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor index 68590c80..8148812d 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor @@ -9,11 +9,24 @@ </p> <div> - <h3>Browser Storage features:</h3> + <h3>Supported Analytics service providers:</h3> <ul> <li><NavLink href="analytics#google">Google Analytics</NavLink></li> </ul> </div> + + <div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="google" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>Google Analytics</h3></Content> + </PermaLinkElement> + <p> + <strong>Google Analytics</strong> is a web analytics service offered by Google that tracks and reports website traffic, etc. inside the Google Marketing Platform. + <br /> + <strong><code>IGoogleAnalyticsService</code> is an injectable service</strong> for enabling + <a href="https://support.google.com/analytics/answer/1008015?hl=en#" target="_blank">Google Analytics</a> page tracking in Blazor Apps. + </p> + </div> + </div> @code { diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/ExampleJsInterop.cs b/src/Majorsoft.Blazor.Extensions.Analytics/ExampleJsInterop.cs deleted file mode 100644 index 807334e7..00000000 --- a/src/Majorsoft.Blazor.Extensions.Analytics/ExampleJsInterop.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Threading.Tasks; - -using Microsoft.JSInterop; - -namespace Majorsoft.Blazor.Extensions.Analytics -{ - // This class provides an example of how JavaScript functionality can be wrapped - // in a .NET class for easy consumption. The associated JavaScript module is - // loaded on demand when first needed. - // - // This class can be registered as scoped DI service and then injected into Blazor - // components for use. - - public class ExampleJsInterop : IAsyncDisposable - { - private readonly Lazy<Task<IJSObjectReference>> moduleTask; - - public ExampleJsInterop(IJSRuntime jsRuntime) - { - moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>( - "import", "./_content/Majorsoft.Blazor.Analytics/exampleJsInterop.js").AsTask()); - } - - public async ValueTask<string> Prompt(string message) - { - var module = await moduleTask.Value; - return await module.InvokeAsync<string>("showPrompt", message); - } - - public async ValueTask DisposeAsync() - { - if (moduleTask.IsValueCreated) - { - var module = await moduleTask.Value; - await module.DisposeAsync(); - } - } - } -} diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs new file mode 100644 index 00000000..9ee3f049 --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading.Tasks; + +using Microsoft.JSInterop; + +namespace Majorsoft.Blazor.Extensions.Analytics.Google +{ + /// <summary> + /// + /// </summary> + public interface IGoogleAnalyticsService : IAsyncDisposable + { + + } + + /// <summary> + /// + /// </summary> + public class GoogleAnalyticsService : IGoogleAnalyticsService + { + private readonly Lazy<Task<IJSObjectReference>> moduleTask; + + public GoogleAnalyticsService(IJSRuntime jsRuntime) + { + moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>( + "import", "./_content/Majorsoft.Blazor.Analytics/googleAnalytics.js").AsTask()); + } + + public async ValueTask Configure(string trackingId) + { + var module = await moduleTask.Value; + await module.InvokeVoidAsync("configure", trackingId); + } + + public async ValueTask DisposeAsync() + { + if (moduleTask.IsValueCreated) + { + var module = await moduleTask.Value; + await module.DisposeAsync(); + } + } + } +} diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml index 10b14e47..6913aca1 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml @@ -4,5 +4,15 @@ <name>Majorsoft.Blazor.Extensions.Analytics</name> </assembly> <members> + <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService"> + <summary> + + </summary> + </member> + <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsService"> + <summary> + + </summary> + </member> </members> </doc> From 8d84c5b548474ae1f0eeac3ad32675075aec1384 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Tue, 6 Apr 2021 22:04:54 +0200 Subject: [PATCH 17/69] Added scroll to page elements AnimateOnHover functionality. --- .github/docs/JsInterop.md | 4 ++++ .../Majorsoft.Blazor.Components.Common.JsInterop.xml | 10 ++++++++++ .../Scroll/ScrollToPageBottom.razor | 10 +++++++++- .../Scroll/ScrollToPageTop.razor | 10 +++++++++- .../Components/JsDemo/ScrollJs.razor | 6 +++++- .../Shared/PageScroll.razor | 4 ++-- 6 files changed, 39 insertions(+), 5 deletions(-) diff --git a/.github/docs/JsInterop.md b/.github/docs/JsInterop.md index d86aa0d2..80667793 100644 --- a/.github/docs/JsInterop.md +++ b/.github/docs/JsInterop.md @@ -127,6 +127,8 @@ Element should be visible when scroll reached page % of given value. Element should be visible until scroll reached page % of given value. - **`SmootScroll`: `bool { get; set; }` (default: true)** <br /> Scroll should be jump or smoothly scroll. +- **`AnimateOnHover`: `bool { get; set; }` (default: true)** <br /> +Apply animation (opacity) on icon when mouse hovered. - **`PaddingFromTop`: `int { get; set; }` (default: 24)** <br /> Required space from page bottom in px. - **`PaddingFromSide`: `int { get; set; }` (default: 24)** <br /> @@ -150,6 +152,8 @@ Required HTML content which will be wrapped into a `<span>` which has the Click Element should be visible when scroll reached page % of given value. - **`SmootScroll`: `bool { get; set; }` (default: true)** <br /> Scroll should be jump or smoothly scroll. +- **`AnimateOnHover`: `bool { get; set; }` (default: true)** <br /> +Apply animation (opacity) on icon when mouse hovered. - **`PaddingFromTop`: `int { get; set; }` (default: 24)** <br /> Required space from page bottom in px. - **`PaddingFromSide`: `int { get; set; }` (default: 24)** <br /> 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 645f1617..fb42b7bd 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 @@ -832,6 +832,11 @@ Scroll should be jump or smoothly scroll. </summary> </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.AnimateOnHover"> + <summary> + Apply animation (opacity) on icon when mouse hovered. + </summary> + </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.PaddingFromTop"> <summary> Required space from page bottom in px. @@ -867,6 +872,11 @@ Scroll should be jump or smoothly scroll. </summary> </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.AnimateOnHover"> + <summary> + Apply animation (opacity) on icon when mouse hovered. + </summary> + </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageTop.PaddingFromBottom"> <summary> Required space from page bottom in px. diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor index 4d32e6a7..df63813a 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor @@ -6,7 +6,10 @@ <style> .scrollToPageBottom { top: @(PaddingFromTop)px; - opacity: @(_isVisible ? 1 : 0); + opacity: @(_isVisible ? (AnimateOnHover ? 0.7 : 1) : 0); + } + .scrollToPageBottom:hover { + opacity: @(_isVisible ? 1 : 0); } .scrollToPageBottom.right { right: @(PaddingFromSide)px; @@ -69,6 +72,11 @@ /// </summary> [Parameter] public bool SmootScroll { get; set; } = true; + /// <summary> + /// Apply animation (opacity) on icon when mouse hovered. + /// </summary> + [Parameter] public bool AnimateOnHover { get; set; } = true; + /// <summary> /// Required space from page bottom in px. /// </summary> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor index be9ba0cf..0e65bac3 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor @@ -6,7 +6,10 @@ <style> .scrollToPageTop { bottom: @(PaddingFromBottom)px; - opacity: @(_isVisible ? 1 : 0); + opacity: @(_isVisible ? (AnimateOnHover ? 0.7 : 1) : 0); + } + .scrollToPageTop:hover { + opacity: @(_isVisible ? 1 : 0); } .scrollToPageTop.right { right: @(PaddingFromSide)px; @@ -65,6 +68,11 @@ /// </summary> [Parameter] public bool SmootScroll { get; set; } = true; + /// <summary> + /// Apply animation (opacity) on icon when mouse hovered. + /// </summary> + [Parameter] public bool AnimateOnHover { get; set; } = true; + /// <summary> /// Required space from page bottom in px. /// </summary> 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 c0e68995..432c6cc2 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor @@ -3,6 +3,7 @@ PaddingFromSide="@_scrcollTopSideMargin" HorizontalPosition="@_scrollHorizontalPosition" SmootScroll="@_smoothScroll" + AnimateOnHover="@_animateOnHover" VisibleFromPagePercentage="2" VisibleUntilPagePercentage="75"> <Content> @@ -16,6 +17,7 @@ PaddingFromSide="@_scrcollTopSideMargin" HorizontalPosition="@_scrollHorizontalPosition" SmootScroll="@_smoothScroll" + AnimateOnHover="@_animateOnHover" VisibleFromPagePercentage="30"> <Content> <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> @@ -67,7 +69,8 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - Smooth scroll (can be set separately for both types of controls): <input type="checkbox" @bind="_smoothScroll" /> + Smooth scroll (can be set separately for both types of controls): <input class="mr-3" type="checkbox" @bind="_smoothScroll" /> + Animate on Hover (can be set separately for both types of controls): <input type="checkbox" @bind="_animateOnHover" /> </div> </div> @@ -157,6 +160,7 @@ private byte _scrcollBefore = 5; private PageScrollHorizontalPosition _scrollHorizontalPosition = PageScrollHorizontalPosition.Right; private bool _smoothScroll = true; + private bool _animateOnHover = true; public async ValueTask DisposeAsync() { diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor index 3cdd2ae6..b58cf3d4 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/PageScroll.razor @@ -1,7 +1,7 @@ @*Scroll on page components wrapped into it's own component and applied on some demo pages*@ <ScrollToPageBottom VisibleFromPagePercentage="2" VisibleUntilPagePercentage="75"> <Content> - <div style="background:whitesmoke; border-radius:16px; width: 32px; height:32px; box-shadow: 1px 2px #e1dddd;"> + <div style="background:whitesmoke; border-radius:16px; width: 32px; height:32px; box-shadow: rgb(0 0 0 / 25%) 0px 3px 6px 0px;"> <i style="padding-left: 6px; padding-top:2px;" class="fas fa-2x fa-angle-down"></i> </div> </Content> @@ -9,7 +9,7 @@ <ScrollToPageTop VisibleFromPagePercentage="20"> <Content> - <div style="background:whitesmoke; border-radius:16px; width: 32px; height:32px; box-shadow: 1px 2px #e1dddd;"> + <div style="background:whitesmoke; border-radius:16px; width: 32px; height:32px; box-shadow: rgb(0 0 0 / 25%) 0px 3px 6px 0px;"> <i style="padding-left: 6px; padding-bottom:2px;" class="fas fa-2x fa-angle-up"></i> </div> </Content> From e8b7d3603920442e2314b05cc92439ea690ff93f Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sat, 10 Apr 2021 20:39:01 +0200 Subject: [PATCH 18/69] Register Google analytics script to HTML --- .../JsInteropExtension.cs | 1 + ...oft.Blazor.Components.Common.JsInterop.xml | 1 + .../Program.cs | 3 ++ ...t.Blazor.Components.TestApps.Common.csproj | 1 + .../AnalyticsExtension.cs | 29 +++++++++++++++++ .../Google/GoogleAnalyticsService.cs | 18 +++++++++-- .../Majorsoft.Blazor.Extensions.Analytics.xml | 13 ++++++++ .../wwwroot/googleAnalytics.js | 31 +++++++++++++++++-- .../wwwroot/googleAnalytics.min.js | 6 +++- 9 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs index 59fd50bb..8970ed0f 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs @@ -23,6 +23,7 @@ public static class JsInteropExtension /// Registers required JS Interop services into IServiceCollection /// </summary> /// <param name="services">IServiceCollection instance</param> + /// <returns>IServiceCollection</returns> public static IServiceCollection AddJsInteropExtensions(this IServiceCollection services) { if (services == null) 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 fb42b7bd..0a396b62 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 @@ -487,6 +487,7 @@ Registers required JS Interop services into IServiceCollection </summary> <param name="services">IServiceCollection instance</param> + <returns>IServiceCollection</returns> </member> <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Language.ILanguageService"> <summary> diff --git a/src/Majorsoft.Blazor.Components.TestApp/Program.cs b/src/Majorsoft.Blazor.Components.TestApp/Program.cs index 633e7b57..bbdbcf61 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/Program.cs +++ b/src/Majorsoft.Blazor.Components.TestApp/Program.cs @@ -11,6 +11,7 @@ using Majorsoft.Blazor.Components.PermaLink; using Majorsoft.Blazor.Components.Maps; using Majorsoft.Blazor.Extensions.BrowserStorage; +using Majorsoft.Blazor.Extensions.Analytics; namespace Majorsoft.Blazor.Components.TestApp { @@ -28,6 +29,8 @@ public static async Task Main(string[] args) builder.Services.AddMapExtensions(); builder.Services.AddBrowserStorage(); + builder.Services.AddGoogleAnalytics("G-1QD2VGTEWX"); + builder.Logging.AddBrowserConsole() .SetMinimumLevel(LogLevel.Debug).AddFilter("Microsoft", LogLevel.Information); diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj b/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj index a4e4d040..ee5cc31c 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj @@ -37,6 +37,7 @@ <ProjectReference Include="..\Majorsoft.Blazor.Components.Timer\Majorsoft.Blazor.Components.Timer.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Toggle\Majorsoft.Blazor.Components.Toggle.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Typeahead\Majorsoft.Blazor.Components.Typeahead.csproj" /> + <ProjectReference Include="..\Majorsoft.Blazor.Extensions.Analytics\Majorsoft.Blazor.Extensions.Analytics.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Extensions.BrowserStorage\Majorsoft.Blazor.Extensions.BrowserStorage.csproj" /> </ItemGroup> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs new file mode 100644 index 00000000..a13e49cd --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs @@ -0,0 +1,29 @@ +using Majorsoft.Blazor.Extensions.Analytics.Google; + +using Microsoft.Extensions.DependencyInjection; + +namespace Majorsoft.Blazor.Extensions.Analytics +{ + /// <summary> + /// Extension methods to register required Analytic services into IServiceCollection + /// </summary> + public static class AnalyticsExtension + { + /// <summary> + /// Registers required Analytic services into IServiceCollection + /// </summary> + /// <param name="services">IServiceCollection instance</param> + /// <param name="trackingId">Google Tracking Id</param> + /// <returns>IServiceCollection</returns> + public static IServiceCollection AddGoogleAnalytics(this IServiceCollection services, string trackingId = null) + { + services.AddSingleton<IGoogleAnalyticsService, GoogleAnalyticsService>(); + if (!string.IsNullOrWhiteSpace(trackingId)) + { + services.BuildServiceProvider().GetRequiredService<IGoogleAnalyticsService>()?.Initialize(trackingId); + } + + return services; + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs index 9ee3f049..9dcda462 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs @@ -10,7 +10,7 @@ namespace Majorsoft.Blazor.Extensions.Analytics.Google /// </summary> public interface IGoogleAnalyticsService : IAsyncDisposable { - + ValueTask Initialize(string trackingId); } /// <summary> @@ -22,8 +22,14 @@ public class GoogleAnalyticsService : IGoogleAnalyticsService public GoogleAnalyticsService(IJSRuntime jsRuntime) { - moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>( - "import", "./_content/Majorsoft.Blazor.Analytics/googleAnalytics.js").AsTask()); + string js = ""; +#if DEBUG + js = "./_content/Majorsoft.Blazor.Extensions.Analytics/googleAnalytics.js"; +#else + js = "./_content/Majorsoft.Blazor.Extensions.Analytics/googleAnalytics.min.js"; +#endif + + moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>("import", js).AsTask()); } public async ValueTask Configure(string trackingId) @@ -32,6 +38,12 @@ public async ValueTask Configure(string trackingId) await module.InvokeVoidAsync("configure", trackingId); } + public async ValueTask Initialize(string trackingId) + { + var module = await moduleTask.Value; + await module.InvokeVoidAsync("init", trackingId); + } + public async ValueTask DisposeAsync() { if (moduleTask.IsValueCreated) diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml index 6913aca1..91b8f63d 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml @@ -4,6 +4,19 @@ <name>Majorsoft.Blazor.Extensions.Analytics</name> </assembly> <members> + <member name="T:Majorsoft.Blazor.Extensions.Analytics.AnalyticsExtension"> + <summary> + Extension methods to register required Analytic services into IServiceCollection + </summary> + </member> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.AnalyticsExtension.AddGoogleAnalytics(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.String)"> + <summary> + Registers required Analytic services into IServiceCollection + </summary> + <param name="services">IServiceCollection instance</param> + <param name="trackingId">Google Tracking Id</param> + <returns>IServiceCollection</returns> + </member> <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService"> <summary> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js index 4c9cb888..3a3e87c7 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js @@ -1,3 +1,30 @@ -export function showPrompt(message) { - return prompt(message, 'Type anything here'); +export function init(trackingId) { + if (!trackingId) { + return; + } + + let src = "https://www.googletagmanager.com/gtag/js?id="; + let scriptTags = document.querySelectorAll('head > script'); + scriptTags.forEach(scriptTag => { + if (scriptTag.getAttribute('src').startsWith(src)) { + return; + } + }); + + //Inject required Google JS scripts to HTML (only once!) + document.head.appendChild(document.createComment("Global site tag (gtag.js) - Google Analytics")); + + let importedGtag = document.createElement('script'); + importedGtag.src = src + trackingId; + importedGtag.async = true; + document.head.appendChild(importedGtag); + + let importedGtagInline = document.createElement('script'); + importedGtagInline.textContent = + `window.dataLayer = window.dataLayer || []; + function gtag() { dataLayer.push(arguments); } + gtag('js', new Date()); + + gtag('config', '{0}');`.replace("{0}", trackingId); + document.head.appendChild(importedGtagInline); } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js index d88e4114..d5c72bcd 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js @@ -1 +1,5 @@ -export function showPrompt(n){return prompt(n,"Type anything here")} \ No newline at end of file +export function init(n){if(n){let i="https://www.googletagmanager.com/gtag/js?id=",u=document.querySelectorAll("head > script");u.forEach(n=>{n.getAttribute("src").startsWith(i)});document.head.appendChild(document.createComment("Global site tag (gtag.js) - Google Analytics"));let t=document.createElement("script");t.src=i+n;t.async=!0;document.head.appendChild(t);let r=document.createElement("script");r.textContent=`window.dataLayer = window.dataLayer || []; + function gtag() { dataLayer.push(arguments); } + gtag('js', new Date()); + + gtag('config', '{0}');`.replace("{0}",n);document.head.appendChild(r)}} \ No newline at end of file From 11cc0231102ff1f83ab269cf859dcfcfc742ce1f Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sun, 11 Apr 2021 20:47:36 +0200 Subject: [PATCH 19/69] Implement IGoogleAnalyticsService --- .../AnalyticsExtension.cs | 4 +- .../Google/GoogleAnalyticsService.cs | 80 +++++++++++++++++-- .../Majorsoft.Blazor.Extensions.Analytics.xml | 52 +++++++++++- .../wwwroot/googleAnalytics.js | 24 ++++++ .../wwwroot/googleAnalytics.min.js | 2 +- 5 files changed, 149 insertions(+), 13 deletions(-) diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs index a13e49cd..37f3fc92 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs @@ -10,10 +10,10 @@ namespace Majorsoft.Blazor.Extensions.Analytics public static class AnalyticsExtension { /// <summary> - /// Registers required Analytic services into IServiceCollection + /// Registers required Google Analytic services into IServiceCollection /// </summary> /// <param name="services">IServiceCollection instance</param> - /// <param name="trackingId">Google Tracking Id</param> + /// <param name="trackingId">Google Tracking Id when provided <see cref="IGoogleAnalyticsService.Initialize(string)"/> will be called.</param> /// <returns>IServiceCollection</returns> public static IServiceCollection AddGoogleAnalytics(this IServiceCollection services, string trackingId = null) { diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs index 9dcda462..4391ddf7 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.JSInterop; @@ -6,20 +8,64 @@ namespace Majorsoft.Blazor.Extensions.Analytics.Google { /// <summary> - /// + /// Injectable service to handle Google analytics gtag.js. /// </summary> public interface IGoogleAnalyticsService : IAsyncDisposable { + /// <summary> + /// Google analytics uniquely Id which was used in <see cref="Initialize(string)"/> method. + /// </summary> + string TrackingId { get; } + + /// <summary> + /// Initialize Google analytics by registering gtag.js to the HTML document. Should be called once. + /// </summary> + /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> + /// <returns>Async ValueTask</returns> ValueTask Initialize(string trackingId); + + /// <summary> + /// Allows you to add additional configuration information to targets. This is typically product-specific configuration for a product + /// such as Google Ads or Google Analytics. + /// </summary> + /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> + /// <param name="configInfo">Is one or more optional parameter-value pairs</param> + /// <returns>Async ValueTask</returns> + ValueTask Config(string trackingId = null, Dictionary<string, object> configInfo = null); + + /// <summary> + /// Allows you to get various values from gtag.js including values set with the set command. + /// </summary> + /// <param name="fieldName">The name of the field to get.</param> + /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> + /// <returns>Async ValueTask</returns> + ValueTask Get(string fieldName, string trackingId = null); + + /// <summary> + /// Allows you to set values that persist across all the subsequent gtag() calls on the page. + /// </summary> + /// <param name="parameterValuePair">Is a key name and the value that is to persist across gtag() calls.</param> + /// <returns>Async ValueTask</returns> + ValueTask Set(Dictionary<string, object> parameterValuePair = null); + + /// <summary> + /// Use the event command to send event data. + /// </summary> + /// <param name="eventName">A recommended event or a custom event name.</param> + /// <param name="eventParams">Is one or more parameter-value pairs.</param> + /// <returns>Async ValueTask</returns> + ValueTask Event(string eventName, Dictionary<string, object> eventParams); } /// <summary> - /// + /// Implementation of <see cref="IGoogleAnalyticsService"/> /// </summary> public class GoogleAnalyticsService : IGoogleAnalyticsService { private readonly Lazy<Task<IJSObjectReference>> moduleTask; + public string TrackingId { get; private set; } + public GoogleAnalyticsService(IJSRuntime jsRuntime) { string js = ""; @@ -32,16 +78,38 @@ public GoogleAnalyticsService(IJSRuntime jsRuntime) moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>("import", js).AsTask()); } - public async ValueTask Configure(string trackingId) + public async ValueTask Initialize(string trackingId) { + if(!string.IsNullOrWhiteSpace(TrackingId) || string.IsNullOrWhiteSpace(trackingId)) //Already initializer or wrong id + { + return; + } + + TrackingId = trackingId; + var module = await moduleTask.Value; - await module.InvokeVoidAsync("configure", trackingId); + await module.InvokeVoidAsync("init", trackingId); } - public async ValueTask Initialize(string trackingId) + public async ValueTask Config(string trackingId = null, Dictionary<string, object> configInfo = null) { var module = await moduleTask.Value; - await module.InvokeVoidAsync("init", trackingId); + await module.InvokeVoidAsync("config", TrackingId, configInfo?.ToList()); + } + public async ValueTask Get(string fieldName, string trackingId = null) + { + var module = await moduleTask.Value; + await module.InvokeVoidAsync("get", TrackingId, fieldName); //TODO: callback results + } + public async ValueTask Set(Dictionary<string, object> parameterValuePair = null) + { + var module = await moduleTask.Value; + await module.InvokeVoidAsync("set", parameterValuePair?.ToList()); + } + public async ValueTask Event(string eventName, Dictionary<string, object> eventParams) + { + var module = await moduleTask.Value; + await module.InvokeVoidAsync("event", eventName, eventParams?.ToList()); } public async ValueTask DisposeAsync() diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml index 91b8f63d..a699fc81 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml @@ -11,20 +11,64 @@ </member> <member name="M:Majorsoft.Blazor.Extensions.Analytics.AnalyticsExtension.AddGoogleAnalytics(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.String)"> <summary> - Registers required Analytic services into IServiceCollection + Registers required Google Analytic services into IServiceCollection </summary> <param name="services">IServiceCollection instance</param> - <param name="trackingId">Google Tracking Id</param> + <param name="trackingId">Google Tracking Id when provided <see cref="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Initialize(System.String)"/> will be called.</param> <returns>IServiceCollection</returns> </member> <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService"> <summary> - + Injectable service to handle Google analytics gtag.js. </summary> </member> + <member name="P:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.TrackingId"> + <summary> + Google analytics uniquely Id which was used in <see cref="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Initialize(System.String)"/> method. + </summary> + </member> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Initialize(System.String)"> + <summary> + Initialize Google analytics by registering gtag.js to the HTML document. Should be called once. + </summary> + <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> + <returns>Async ValueTask</returns> + </member> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Config(System.String,System.Collections.Generic.Dictionary{System.String,System.Object})"> + <summary> + Allows you to add additional configuration information to targets. This is typically product-specific configuration for a product + such as Google Ads or Google Analytics. + </summary> + <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> + <param name="configInfo">Is one or more optional parameter-value pairs</param> + <returns>Async ValueTask</returns> + </member> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Get(System.String,System.String)"> + <summary> + Allows you to get various values from gtag.js including values set with the set command. + </summary> + <param name="fieldName">The name of the field to get.</param> + <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> + <returns>Async ValueTask</returns> + </member> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Set(System.Collections.Generic.Dictionary{System.String,System.Object})"> + <summary> + Allows you to set values that persist across all the subsequent gtag() calls on the page. + </summary> + <param name="parameterValuePair">Is a key name and the value that is to persist across gtag() calls.</param> + <returns>Async ValueTask</returns> + </member> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Event(System.String,System.Collections.Generic.Dictionary{System.String,System.Object})"> + <summary> + Use the event command to send event data. + </summary> + <param name="eventName">A recommended event or a custom event name.</param> + <param name="eventParams">Is one or more parameter-value pairs.</param> + <returns>Async ValueTask</returns> + </member> <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsService"> <summary> - + Implementation of <see cref="T:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService"/> </summary> </member> </members> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js index 3a3e87c7..22c893d1 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js @@ -27,4 +27,28 @@ export function init(trackingId) { gtag('config', '{0}');`.replace("{0}", trackingId); document.head.appendChild(importedGtagInline); +} + +//Google Gtag API: https://developers.google.com/gtagjs/reference/api +export function config(trackingId, data) { + if (trackingId && window.gtag) { + gtag('config', trackingId, data); + } +} +export function get(trackingId, fieldName) { + if (trackingId && window.gtag) { + gtag('get', trackingId, fieldName, (result) => { + //callback method here... + }); + } +} +export function set(data) { + if (window.gtag && data) { + gtag('set', trackingId, data); + } +} +export function event(eventName, data) { + if (window.gtag && eventName && data) { + gtag('event', eventName, data); + } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js index d5c72bcd..5594c0f8 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js @@ -2,4 +2,4 @@ export function init(n){if(n){let i="https://www.googletagmanager.com/gtag/js?id function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); - gtag('config', '{0}');`.replace("{0}",n);document.head.appendChild(r)}} \ No newline at end of file + gtag('config', '{0}');`.replace("{0}",n);document.head.appendChild(r)}}export function config(n,t){n&&window.gtag&>ag("config",n,t)}export function get(n,t){n&&window.gtag&>ag("get",n,t,()=>{})}export function set(n){window.gtag&&n&>ag("set",trackingId,n)}export function event(n,t){window.gtag&&n&&t&>ag("event",n,t)} \ No newline at end of file From 6cd3348e2bbfef0ef694a0322e2095e45927e3ef Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sat, 29 May 2021 17:21:58 +0200 Subject: [PATCH 20/69] Analytics fixes --- .../Google/GoogleAnalyticsService.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs index 4391ddf7..2b453ada 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs @@ -31,7 +31,7 @@ public interface IGoogleAnalyticsService : IAsyncDisposable /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> /// <param name="configInfo">Is one or more optional parameter-value pairs</param> /// <returns>Async ValueTask</returns> - ValueTask Config(string trackingId = null, Dictionary<string, object> configInfo = null); + ValueTask Config(string trackingId = "", Dictionary<string, object> configInfo = null); /// <summary> /// Allows you to get various values from gtag.js including values set with the set command. @@ -39,7 +39,7 @@ public interface IGoogleAnalyticsService : IAsyncDisposable /// <param name="fieldName">The name of the field to get.</param> /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> /// <returns>Async ValueTask</returns> - ValueTask Get(string fieldName, string trackingId = null); + ValueTask Get(string fieldName, string trackingId = ""); /// <summary> /// Allows you to set values that persist across all the subsequent gtag() calls on the page. @@ -91,15 +91,15 @@ public async ValueTask Initialize(string trackingId) await module.InvokeVoidAsync("init", trackingId); } - public async ValueTask Config(string trackingId = null, Dictionary<string, object> configInfo = null) + public async ValueTask Config(string trackingId = "", Dictionary<string, object> configInfo = null) { var module = await moduleTask.Value; - await module.InvokeVoidAsync("config", TrackingId, configInfo?.ToList()); + await module.InvokeVoidAsync("config", string.IsNullOrWhiteSpace(trackingId) ? TrackingId : trackingId, configInfo?.ToList()); } - public async ValueTask Get(string fieldName, string trackingId = null) + public async ValueTask Get(string fieldName, string trackingId = "") { var module = await moduleTask.Value; - await module.InvokeVoidAsync("get", TrackingId, fieldName); //TODO: callback results + await module.InvokeVoidAsync("get", string.IsNullOrWhiteSpace(trackingId) ? TrackingId : trackingId, fieldName); //TODO: callback results } public async ValueTask Set(Dictionary<string, object> parameterValuePair = null) { @@ -121,4 +121,4 @@ public async ValueTask DisposeAsync() } } } -} +} \ No newline at end of file From 0ca63c82b59bdba871a9b73a36b2a196426399ff Mon Sep 17 00:00:00 2001 From: Nick Kovalsky <nickyboom@mail.ru> Date: Tue, 8 Jun 2021 22:15:53 +0300 Subject: [PATCH 21/69] more fixes same kind --- .../wwwroot/scroll.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js index 5447fd7c..2c3a6332 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js @@ -124,8 +124,8 @@ export function getPageScrollPosition() { let left = window.pageXOffset || document.documentElement.scrollLeft; let args = { - X: top, - Y: left + X: left, + Y: top }; return args; @@ -138,8 +138,8 @@ function createScrollEventHandler(dotnetRef) { let left = window.pageXOffset || document.documentElement.scrollLeft; let args = { - X: top, - Y: left + X: left, + Y: top }; dotnetRef.invokeMethodAsync("PageScroll", args); @@ -213,4 +213,4 @@ export function dispose(eventIdArray) { removeScrollEvent(eventIdArray[i]); } } -} \ No newline at end of file +} From b2ef092688e80ab65e442d61056ffd991c331b77 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Thu, 3 Jun 2021 11:26:17 +0200 Subject: [PATCH 22/69] XML cleanup. --- .../Blazor.Components.ColorPicker.xml | 8 - .../Blazor.Components.Common.JsInterop.xml | 434 ------------------ .../Blazor.Components.CssEvents.xml | 191 -------- .../Blazor.Components.Debounce.xml | 8 - .../Blazor.Components.DragAndDrop.xml | 8 - .../Blazor.Components.Loading.xml | 8 - .../Blazor.Components.Modal.xml | 8 - .../Blazor.Components.PermaLink.xml | 41 -- .../Blazor.Components.Tabs.xml | 123 ----- .../Components/Dialog.razor | 26 +- .../Blazor.Components.Timer.xml | 37 -- .../Blazor.Components.Toggle.xml | 178 ------- .../Blazor.Components.Typeahead.xml | 8 - .../Google/GoogleAnalyticsService.cs | 8 +- 14 files changed, 17 insertions(+), 1069 deletions(-) delete mode 100644 src/Majorsoft.Blazor.Components.ColorPicker/Blazor.Components.ColorPicker.xml delete mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/Blazor.Components.Common.JsInterop.xml delete mode 100644 src/Majorsoft.Blazor.Components.CssEvents/Blazor.Components.CssEvents.xml delete mode 100644 src/Majorsoft.Blazor.Components.Debounce/Blazor.Components.Debounce.xml delete mode 100644 src/Majorsoft.Blazor.Components.DragAndDrop/Blazor.Components.DragAndDrop.xml delete mode 100644 src/Majorsoft.Blazor.Components.Loading/Blazor.Components.Loading.xml delete mode 100644 src/Majorsoft.Blazor.Components.Modal/Blazor.Components.Modal.xml delete mode 100644 src/Majorsoft.Blazor.Components.PermaLink/Blazor.Components.PermaLink.xml delete mode 100644 src/Majorsoft.Blazor.Components.Tabs/Blazor.Components.Tabs.xml delete mode 100644 src/Majorsoft.Blazor.Components.Timer/Blazor.Components.Timer.xml delete mode 100644 src/Majorsoft.Blazor.Components.Toggle/Blazor.Components.Toggle.xml delete mode 100644 src/Majorsoft.Blazor.Components.Typeahead/Blazor.Components.Typeahead.xml diff --git a/src/Majorsoft.Blazor.Components.ColorPicker/Blazor.Components.ColorPicker.xml b/src/Majorsoft.Blazor.Components.ColorPicker/Blazor.Components.ColorPicker.xml deleted file mode 100644 index ed6b46a5..00000000 --- a/src/Majorsoft.Blazor.Components.ColorPicker/Blazor.Components.ColorPicker.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.ColorPicker</name> - </assembly> - <members> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Blazor.Components.Common.JsInterop.xml b/src/Majorsoft.Blazor.Components.Common.JsInterop/Blazor.Components.Common.JsInterop.xml deleted file mode 100644 index 6ce08572..00000000 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Blazor.Components.Common.JsInterop.xml +++ /dev/null @@ -1,434 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.Common.JsInterop</name> - </assembly> - <members> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Click.ClickBoundariesEventInfo"> - <summary> - ClickBoundariesEventInfo event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Click.ClickBoundariesHandler"> - <summary> - Implementation of <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Click.IClickBoundariesHandler"/> - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Click.IClickBoundariesHandler"> - <summary> - Injectable service to handle JS 'click' events for the whole document. - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Click.IClickBoundariesHandler.RegisterClickBoundariesAsync(Microsoft.AspNetCore.Components.ElementReference,System.Func{Microsoft.AspNetCore.Components.Web.MouseEventArgs,System.Threading.Tasks.Task},System.Func{Microsoft.AspNetCore.Components.Web.MouseEventArgs,System.Threading.Tasks.Task})"> - <summary> - Adds event listener for 'click' HTML event for the given element with property filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="outsideClickCallback">Func to call when clicked outside of the given element</param> - <param name="insideClickCallback">Func to call when clicked inside of the given element</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Click.IClickBoundariesHandler.RemoveClickBoundariesAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Removes event listener for 'click' HTML event for the given element. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <returns>Async Task</returns> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Clipboard.ClipboardExtensions"> - <summary> - Static class for extension methods - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Clipboard.ClipboardExtensions.CopyElementTextToClipboardAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Copies the given element text content to clipboard. - </summary> - <param name="elementReference">ElementReference to get text</param> - <returns>Async Task</returns> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Clipboard.ClipboardHandler"> - <summary> - Implementation of <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Clipboard.IClipboardHandler"/> - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Clipboard.IClipboardHandler"> - <summary> - Injectable service to handle JS 'copy' to clipboard Interops. - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Clipboard.IClipboardHandler.CopyElementTextToClipboardAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Copies the given element text content to clipboard. - </summary> - <param name="elementReference">ElementReference to get text</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Clipboard.IClipboardHandler.CopyTextToClipboardAsync(System.String)"> - <summary> - Copies the given text content to clipboard. - </summary> - <param name="elementReference">Text to copy</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.ElementInfo.ElementReferenceInfoExtensions.GetClientRectAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Returns the given HTML element ClintBoundRect data. - </summary> - <param name="elementReference">Blazor reference to an HTML element</param> - <returns>Async Task with <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.ElementInfo.DomRect"/> value</returns> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Focus.FocusHandler"> - <summary> - Implementation of <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Focus.IFocusHandler"/> - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Focus.IFocusHandler"> - <summary> - Injectable service to handle JS 'focus' Interops. - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Focus.IFocusHandler.GetFocusedElementAsync"> - <summary> - Returns the actually focused HTML DOM element reference. It works with outside element of a Blazor component. - Note: <see cref="T:Microsoft.JSInterop.IJSObjectReference"/> object is disposable. - </summary> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Focus.IFocusHandler.FocusElementAsync(Microsoft.JSInterop.IJSObjectReference)"> - <summary> - Sets focus on the given HTML DOM element reference - Note: <see cref="T:Microsoft.JSInterop.IJSObjectReference"/> object is disposable. - </summary> - <param name="objectReference">IJSObjectReference to set focus on</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Focus.IFocusHandler.FocusElementAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Sets focus on the given HTML DOM element reference - </summary> - <param name="elementReference">ElementReference to set focus on</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Focus.IFocusHandler.StoreFocusedElementAsync"> - <summary> - Stores the actually focused HTML DOM element reference into a JS variable. This can be restored by calling <code>RestoreStoredElementFocusAsync</code> method. - </summary> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Focus.IFocusHandler.RestoreStoredElementFocusAsync(System.Boolean)"> - <summary> - Restores the HTML DOM element reference stored by calling <code>StoreFocusedElementAsync</code> method. - </summary> - <returns>Async Task</returns> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.GlobalMouseEventHandler"> - <summary> - Implementation of <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.IGlobalMouseEventHandler"/> - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.IGlobalMouseEventHandler"> - <summary> - Injectable service to handle JS document/window 'mouse' events for the whole document. - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.IGlobalMouseEventHandler.RegisterPageMouseMoveAsync(System.Func{Microsoft.AspNetCore.Components.Web.MouseEventArgs,System.Threading.Tasks.Task})"> - <summary> - Adds event listener for mouse 'move' HTML event for the whole document/window. - </summary> - <param name="mouseMoveCallback">Func to call when mouse move happened</param> - <returns>Async Task with event id to unsubscribe from event</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.IGlobalMouseEventHandler.RemovePageMouseMoveAsync(System.String)"> - <summary> - Removes event listener for mouse 'move' HTML event for the whole document/window by the given event Id. - </summary> - <param name="eventId">Event id from <see cref="M:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.IGlobalMouseEventHandler.RegisterPageMouseMoveAsync(System.Func{Microsoft.AspNetCore.Components.Web.MouseEventArgs,System.Threading.Tasks.Task})"/></param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.IGlobalMouseEventHandler.RegisterPageMouseDownAsync(System.Func{Microsoft.AspNetCore.Components.Web.MouseEventArgs,System.Threading.Tasks.Task})"> - <summary> - Adds event listener for mouse 'down' HTML event for the whole document/window. - </summary> - <param name="mouseDownCallback">Func to call when mouse down happened</param> - <returns>Async Task with event id to unsubscribe from event</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.IGlobalMouseEventHandler.RemovePageMouseDownAsync(System.String)"> - <summary> - Removes event listener for mouse 'down' HTML event for the whole document/window by the given event Id. - </summary> - <param name="eventId">Event id from <see cref="M:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.IGlobalMouseEventHandler.RegisterPageMouseDownAsync(System.Func{Microsoft.AspNetCore.Components.Web.MouseEventArgs,System.Threading.Tasks.Task})"/></param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.IGlobalMouseEventHandler.RegisterPageMouseUpAsync(System.Func{Microsoft.AspNetCore.Components.Web.MouseEventArgs,System.Threading.Tasks.Task})"> - <summary> - Adds event listener for mouse 'up' HTML event for the whole document/window. - </summary> - <param name="mouseUpCallback">Func to call when mouse move happened</param> - <returns>Async Task with event id to unsubscribe from event</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.IGlobalMouseEventHandler.RemovePageMouseUpAsync(System.String)"> - <summary> - Removes event listener for mouse 'up' HTML event for the whole document/window by the given event Id. - </summary> - <param name="eventId">Event id from <see cref="M:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.IGlobalMouseEventHandler.RegisterPageMouseUpAsync(System.Func{Microsoft.AspNetCore.Components.Web.MouseEventArgs,System.Threading.Tasks.Task})"/></param> - <returns>Async Task</returns> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.PageMouseEventInfo"> - <summary> - PageMouseEventInfo event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.ResizeEventArgs"> - <summary> - Custom defined event args for Resize events - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.ResizeEventArgs.Height"> - <summary> - Element or Window height - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.ResizeEventArgs.Width"> - <summary> - Element or Window width - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.ResizeEventArgs.EventId"> - <summary> - Registered event id - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.ResizeEventInfo"> - <summary> - PageResizeEventInfo event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.JsInteropExtension"> - <summary> - Extension methods to register required JS Interop services into IServiceCollection - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.JsInteropExtension.AddJsInteropExtensions(Microsoft.Extensions.DependencyInjection.IServiceCollection)"> - <summary> - Registers required JS Interop services into IServiceCollection - </summary> - <param name="services">IServiceCollection instance</param> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Resize.IResizeHandler"> - <summary> - Injectable service to handle JS 'resize' events for HTML element or the whole document. - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Resize.IResizeHandler.RegisterPageResizeAsync(System.Func{Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.ResizeEventArgs,System.Threading.Tasks.Task})"> - <summary> - Adds event listener for 'resize' HTML event for the whole document/window. - </summary> - <param name="resizeCallback">Func to call when page resize happened</param> - <returns>Async Task with event id to unsubscribe from event</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Resize.IResizeHandler.RemovePageResizeAsync(System.String)"> - <summary> - Removes event listener for 'resize' HTML event for the whole document/window by the given event Id. - </summary> - <param name="eventId">Event id from <see cref="M:Majorsoft.Blazor.Components.Common.JsInterop.Resize.IResizeHandler.RegisterPageResizeAsync(System.Func{Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.ResizeEventArgs,System.Threading.Tasks.Task})"/></param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Resize.IResizeHandler.RegisterResizeAsync(Microsoft.AspNetCore.Components.ElementReference,System.Func{Majorsoft.Blazor.Components.Common.JsInterop.GlobalMouseEvents.ResizeEventArgs,System.Threading.Tasks.Task})"> - <summary> - Adds event listener for 'resize' HTML event for the given element with property filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="resizeCallback">Func to call when the given element was resized</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Resize.IResizeHandler.RemoveResizeAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Removes event listener for 'resize' HTML event for the given element. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <returns>Async Task</returns> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Resize.ResizeHandler"> - <summary> - Implementation of <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Resize.IResizeHandler"/> - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions"> - <summary> - Extensions for <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> HTML elements. - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.ScrollToElementAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Scrolls HTML page to given element - </summary> - <param name="elementReference">Blazor reference to an HTML element</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.ScrollToEndAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Scrolls inside the given element to the bottom (end). - </summary> - <param name="elementReference">Blazor reference to an HTML element</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.ScrollToTopAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Scrolls inside the given element to the beginning (top). - </summary> - <param name="elementReference">Blazor reference to an HTML element</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.ScrollToXAsync(Microsoft.AspNetCore.Components.ElementReference,System.Double)"> - <summary> - Scrolls inside the given element to the given X position. - </summary> - <param name="elementReference">Blazor reference to an HTML element</param> - <param name="xPos">Scroll X position</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.ScrollToYAsync(Microsoft.AspNetCore.Components.ElementReference,System.Double)"> - <summary> - Scrolls inside the given element to the given Y position. - </summary> - <param name="elementReference">Blazor reference to an HTML element</param> - <param name="yPos">Scroll Y position</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.GetScrollXPositionAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Returns given element scroll X position. - </summary> - <param name="elementReference">Blazor reference to an HTML element</param> - <returns>Async Task with X pos</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.IsElementHiddenAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Returns given element is visible on HTML document or not. - </summary> - <param name="elementReference">Blazor reference to an HTML element</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.IsElementHiddenBelowAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Returns given element is below of the view port. - </summary> - <param name="elementReference">Blazor reference to an HTML element</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.IsElementHiddenAboveAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Returns given element is above of the view port. - </summary> - <param name="elementReference">Blazor reference to an HTML element</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.ScrollToElementInParentAsync(Microsoft.AspNetCore.Components.ElementReference,Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Scrolls inside the given parent element to the given inner element. - </summary> - <param name="parent">Blazor reference to an HTML (outer/wrapper) element</param> - <param name="innerElement">Blazor reference to an inner HTML element to scroll to</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.ScrollInParentByIdAsync(Microsoft.AspNetCore.Components.ElementReference,System.String)"> - <summary> - Scrolls inside the given parent element to the given inner element by Id. - </summary> - <param name="parent">Blazor reference to an HTML (outer/wrapper) element</param> - <param name="id">Inner element Id to scroll to</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ElementReferenceScrollExtensions.ScrollInParentByClassAsync(Microsoft.AspNetCore.Components.ElementReference,System.String)"> - <summary> - Scrolls inside the given parent element to the given first found inner element by class name. - </summary> - <param name="parent">Blazor reference to an HTML (outer/wrapper) element</param> - <param name="className">Inner element CSS class to scroll to</param> - <returns>Async Task</returns> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler"> - <summary> - Injectable service to handle JS document/window 'scroll' events for the whole document. - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToElementAsync(Microsoft.AspNetCore.Components.ElementReference)"> - <summary> - Scrolls the given element into the page view area. - </summary> - <param name="elementReference">Blazor reference to an HTML element</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToElementByIdAsync(System.String)"> - <summary> - Finds element by Id and scrolls the given element into the page view area. - </summary> - <param name="name">DOM element id</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToElementByNameAsync(System.String)"> - <summary> - Finds element by name and scrolls the given element into the page view area. - </summary> - <param name="name">DOM element name</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageEndAsync"> - <summary> - Scrolls to end of the page (X bottom). - </summary> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageTopAsync"> - <summary> - Scrolls to top of the page (X top). - </summary> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageXAsync(System.Double)"> - <summary> - Scrolls to X position on the page. - </summary> - <param name="x">Scroll top x value</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.ScrollToPageYAsync(System.Double)"> - <summary> - Scrolls to Y position on the page. - </summary> - <param name="x">Scroll top x value</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.GetPageScrollPosAsync"> - <summary> - Returns page X,Y scroll position as <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollEventArgs"/>. - </summary> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.RegisterPageScrollAsync(System.Func{Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollEventArgs,System.Threading.Tasks.Task})"> - <summary> - Adds event listener for 'scroll' HTML event for the whole document/window. - </summary> - <param name="scrollCallback">Func to call when scroll happened</param> - <returns>Async Task with event id to unsubscribe from event</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.RemovePageScrollAsync(System.String)"> - <summary> - Removes event listener for 'scroll' HTML event for the whole document/window by the given event Id. - </summary> - <param name="eventId">Event id from <see cref="M:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler.RegisterPageScrollAsync(System.Func{Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollEventArgs,System.Threading.Tasks.Task})"/></param> - <returns>Async Task</returns> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.PageScrollEventInfo"> - <summary> - PageScrollEventInfo event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollHandler"> - <summary> - Implementation of <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.IScrollHandler"/> - </summary> - </member> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Components.CssEvents/Blazor.Components.CssEvents.xml b/src/Majorsoft.Blazor.Components.CssEvents/Blazor.Components.CssEvents.xml deleted file mode 100644 index 4100b0fa..00000000 --- a/src/Majorsoft.Blazor.Components.CssEvents/Blazor.Components.CssEvents.xml +++ /dev/null @@ -1,191 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.CssEvents</name> - </assembly> - <members> - <member name="T:Majorsoft.Blazor.Components.CssEvents.Animation.AnimationCollectionInfo"> - <summary> - Collection of <see cref="T:Majorsoft.Blazor.Components.CssEvents.Animation.AnimationEventInfo"/> to aggregate Animation events - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.CssEvents.Animation.AnimationEventInfo"> - <summary> - Animation event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService"> - <summary> - Injectable service to handle CSS Animation events - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService.RegisterAnimationStartedAsync(Microsoft.AspNetCore.Components.ElementReference,System.Func{Majorsoft.Blazor.Components.CssEvents.Animation.AnimationEventArgs,System.Threading.Tasks.Task},System.String)"> - <summary> - Adds event listener for 'animationstart' HTML event for the given element with Animation name filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="onStartedCallback">Func to call when Animation event has started fired</param> - <param name="animationName">Animation name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService.RemoveAnimationStartedAsync(Microsoft.AspNetCore.Components.ElementReference,System.String)"> - <summary> - Removes event listener for 'animationstart' HTML event for the given element with Animation name filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="animationName">Animation name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService.RegisterAnimationIterationAsync(Microsoft.AspNetCore.Components.ElementReference,System.Func{Majorsoft.Blazor.Components.CssEvents.Animation.AnimationEventArgs,System.Threading.Tasks.Task},System.String)"> - <summary> - Adds event listener for 'animationiteration' HTML event for the given element with Animation name filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="onIterationCallback">Func to call when Animation event has started new iteration fired</param> - <param name="animationName">Animation name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService.RemoveAnimationIterationAsync(Microsoft.AspNetCore.Components.ElementReference,System.String)"> - <summary> - Removes event listener for 'animationiteration' HTML event for the given element with Animation name filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="animationName">Animation name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService.RegisterAnimationEndedAsync(Microsoft.AspNetCore.Components.ElementReference,System.Func{Majorsoft.Blazor.Components.CssEvents.Animation.AnimationEventArgs,System.Threading.Tasks.Task},System.String)"> - <summary> - Adds event listener for 'animationend' HTML event for the given element with Animation name filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="onEndedCallback">Func to call when Animation event has finished fired</param> - <param name="animationName">Animation name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService.RemoveAnimationEndedAsync(Microsoft.AspNetCore.Components.ElementReference,System.String)"> - <summary> - Removes event listener for 'animationend' HTML event for the given element with Animation name filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="animationName">Animation name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService.RegisterAllAnimationEventsAsync(Microsoft.AspNetCore.Components.ElementReference,System.Func{Majorsoft.Blazor.Components.CssEvents.Animation.AnimationEventArgs,System.Threading.Tasks.Task},System.String)"> - <summary> - Adds event listeners with single callback for all supported HTML events for the given element with Animation name filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="onEventCallback">Func to call when any Animation event fired</param> - <param name="animationName">Animation name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService.RegisterAllAnimationEventsAsync(Microsoft.AspNetCore.Components.ElementReference,System.Func{Majorsoft.Blazor.Components.CssEvents.Animation.AnimationEventArgs,System.Threading.Tasks.Task},System.Func{Majorsoft.Blazor.Components.CssEvents.Animation.AnimationEventArgs,System.Threading.Tasks.Task},System.Func{Majorsoft.Blazor.Components.CssEvents.Animation.AnimationEventArgs,System.Threading.Tasks.Task},System.String)"> - <summary> - Adds event listeners with different callbacks for all supported HTML events for the given element with Animation name filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="onStartedCallback">Func to call when Animation event has started fired</param> - <param name="onIterationCallback">Func to call when Animation event has started new iteration fired</param> - <param name="onEndedCallback">Func to call when Animation event has finished fired</param> - <param name="animationName">Animation name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService.RemoveAllAnimationEventsAsync(Microsoft.AspNetCore.Components.ElementReference,System.String)"> - <summary> - Removes event listener for all supported HTML event for the given element with Animation name filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="animationName">Animation name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService.RegisterAnimationsWhenAllEndedAsync(System.Func{Majorsoft.Blazor.Components.CssEvents.Animation.AnimationEventArgs[],System.Threading.Tasks.Task},System.Collections.Generic.KeyValuePair{Microsoft.AspNetCore.Components.ElementReference,System.String}[])"> - <summary> - Adds event listeners for 'animationend' HTML event for the given elements with Animation names filters. - </summary> - <param name="onEndedCallback">Func to call when ALL Animation events has finished</param> - <param name="elementRefsWithProperties">Params KeyValuePair with Blazor reference to an HTML element and Animation name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService.RemoveAnimationsWhenAllEndedAsync(System.Collections.Generic.KeyValuePair{Microsoft.AspNetCore.Components.ElementReference,System.String}[])"> - <summary> - Removes event listeners for 'animationend' HTML event for the given elements with Animation names filters. - </summary> - <param name="elementRefsWithProperties">Params KeyValuePair with Blazor reference to an HTML element and Animation name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="T:Majorsoft.Blazor.Components.CssEvents.CssBaseEventArgs"> - <summary> - Common properties of CSS Animation and Transition events. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.CssEvents.CssBaseEventArgs.Element"> - <summary> - Original element Ref - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.CssEvents.CssEventsExtension"> - <summary> - Extension methods to register <see cref="T:Majorsoft.Blazor.Components.CssEvents.Transition.ITransitionEventsService"/> and <see cref="T:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService"/> into IServiceCollection - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.CssEventsExtension.AddCssEvents(Microsoft.Extensions.DependencyInjection.IServiceCollection)"> - <summary> - Registers <see cref="T:Majorsoft.Blazor.Components.CssEvents.Transition.ITransitionEventsService"/> and <see cref="T:Majorsoft.Blazor.Components.CssEvents.Animation.IAnimationEventsService"/> as Transient - to IServiceCollection in order to be able to inject CSS events... - </summary> - <param name="services">IServiceCollection instance</param> - </member> - <member name="T:Majorsoft.Blazor.Components.CssEvents.Transition.ITransitionEventsService"> - <summary> - Injectable service to handle CSS Transition events - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Transition.ITransitionEventsService.RegisterTransitionEndedAsync(Microsoft.AspNetCore.Components.ElementReference,System.Func{Majorsoft.Blazor.Components.CssEvents.Transition.TransitionEventArgs,System.Threading.Tasks.Task},System.String)"> - <summary> - Adds event listener for 'transitionend' HTML event for the given element with property filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="onEndedCallback">Func to call when Transition event has finished</param> - <param name="transitionPropertyName">Transition property name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Transition.ITransitionEventsService.RegisterTransitionsWhenAllEndedAsync(System.Func{Majorsoft.Blazor.Components.CssEvents.Transition.TransitionEventArgs[],System.Threading.Tasks.Task},System.Collections.Generic.KeyValuePair{Microsoft.AspNetCore.Components.ElementReference,System.String}[])"> - <summary> - Adds event listeners for 'transitionend' HTML event for the given elements with property filters. - </summary> - <param name="onEndedCallback">Func to call when all Transition events has finished</param> - <param name="elementRefsWithProperties">Params KeyValuePair with Blazor reference to an HTML element and property name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Transition.ITransitionEventsService.RemoveTransitionEndedAsync(Microsoft.AspNetCore.Components.ElementReference,System.String)"> - <summary> - Removes event listener for 'transitionend' HTML event for the given element with property filter. - </summary> - <param name="elementRef">Blazor reference to an HTML element</param> - <param name="transitionPropertyName">Transition property name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.CssEvents.Transition.ITransitionEventsService.RemoveTransitionsWhenAllEndedAsync(System.Collections.Generic.KeyValuePair{Microsoft.AspNetCore.Components.ElementReference,System.String}[])"> - <summary> - Removes event listeners for 'transitionend' HTML event for the given elements with property filters. - </summary> - <param name="elementRefsWithProperties">Params KeyValuePair with Blazor reference to an HTML element and property name for filter event</param> - <returns>Async Task</returns> - </member> - <member name="T:Majorsoft.Blazor.Components.CssEvents.Transition.TransitionCollectionInfo"> - <summary> - Collection of <see cref="T:Majorsoft.Blazor.Components.CssEvents.Transition.TransitionEventInfo"/> to aggregate Transition events - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.CssEvents.Transition.TransitionEventInfo"> - <summary> - Transition event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.CssEvents.Transition.TransitionEventsService"> - <summary> - Implementation of <see cref="T:Majorsoft.Blazor.Components.CssEvents.Transition.ITransitionEventsService"/> - </summary> - </member> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Components.Debounce/Blazor.Components.Debounce.xml b/src/Majorsoft.Blazor.Components.Debounce/Blazor.Components.Debounce.xml deleted file mode 100644 index 9665a406..00000000 --- a/src/Majorsoft.Blazor.Components.Debounce/Blazor.Components.Debounce.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.Debounce</name> - </assembly> - <members> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Components.DragAndDrop/Blazor.Components.DragAndDrop.xml b/src/Majorsoft.Blazor.Components.DragAndDrop/Blazor.Components.DragAndDrop.xml deleted file mode 100644 index bd342e37..00000000 --- a/src/Majorsoft.Blazor.Components.DragAndDrop/Blazor.Components.DragAndDrop.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.DragAndDrop</name> - </assembly> - <members> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Components.Loading/Blazor.Components.Loading.xml b/src/Majorsoft.Blazor.Components.Loading/Blazor.Components.Loading.xml deleted file mode 100644 index 904ccb37..00000000 --- a/src/Majorsoft.Blazor.Components.Loading/Blazor.Components.Loading.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.Loading</name> - </assembly> - <members> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Components.Modal/Blazor.Components.Modal.xml b/src/Majorsoft.Blazor.Components.Modal/Blazor.Components.Modal.xml deleted file mode 100644 index 8f1201a5..00000000 --- a/src/Majorsoft.Blazor.Components.Modal/Blazor.Components.Modal.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.Modal</name> - </assembly> - <members> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Components.PermaLink/Blazor.Components.PermaLink.xml b/src/Majorsoft.Blazor.Components.PermaLink/Blazor.Components.PermaLink.xml deleted file mode 100644 index 4e6ae814..00000000 --- a/src/Majorsoft.Blazor.Components.PermaLink/Blazor.Components.PermaLink.xml +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.PermaLink</name> - </assembly> - <members> - <member name="T:Majorsoft.Blazor.Components.PermaLink.EventHandlers"> - <summary> - Event handler to fix blinking icon - https://stackoverflow.com/questions/63265941/blazor-3-1-nested-onmouseover-events - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.PermaLink.IPermaLinkWatcherService"> - <summary> - Injectable service to handle Permalink navigation for the whole application. It is registered as Singleton - and should be injected only once for the whole application. Best way to use MainLayout.razor. - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.PermaLink.IPermaLinkWatcherService.WatchPermaLinks"> - <summary> - Starts a navigation watcher which will check for Permalinks in the URLs. - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.PermaLink.PermaLinkExtensions"> - <summary> - Extension methods to register required Permalink services into IServiceCollection - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.PermaLink.PermaLinkExtensions.AddPermaLinkWatcher(Microsoft.Extensions.DependencyInjection.IServiceCollection)"> - <summary> - Registers required Permalink services into IServiceCollection - </summary> - <param name="services">IServiceCollection instance</param> - </member> - <member name="T:Majorsoft.Blazor.Components.PermaLink.PermaLinkWatcherService"> - <summary> - Implementation of <see cref="T:Majorsoft.Blazor.Components.PermaLink.IPermaLinkWatcherService"/> - </summary> - </member> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Components.Tabs/Blazor.Components.Tabs.xml b/src/Majorsoft.Blazor.Components.Tabs/Blazor.Components.Tabs.xml deleted file mode 100644 index 7b2ee73e..00000000 --- a/src/Majorsoft.Blazor.Components.Tabs/Blazor.Components.Tabs.xml +++ /dev/null @@ -1,123 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.Tabs</name> - </assembly> - <members> - <member name="T:Majorsoft.Blazor.Components.Tabs.TabPositons"> - <summary> - Determines the positions of tabs. - </summary> - </member> - <member name="F:Majorsoft.Blazor.Components.Tabs.TabPositons.Left"> - <summary> - Left side - </summary> - </member> - <member name="F:Majorsoft.Blazor.Components.Tabs.TabPositons.Center"> - <summary> - Centered - </summary> - </member> - <member name="F:Majorsoft.Blazor.Components.Tabs.TabPositons.Right"> - <summary> - Right side - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabItem.Header"> - <summary> - Required HTML content to show Header content of current TabItem. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabItem.Content"> - <summary> - Required HTML content to show content of current TabItem. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabItem.Disabled"> - <summary> - Determines whether all the rendered HTML elements should be disabled or not. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabItem.AllOtherAttributes"> - <summary> - Blazor capture for any unmatched HTML attributes. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.InnerElementReference"> - <summary> - Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.TabItems"> - <summary> - Required HTML content to set TabItems as <see cref="T:Microsoft.AspNetCore.Components.RenderFragment"/>. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.ActiveColor"> - <summary> - Sets the `style` of the `background-color` when tab is Active. Use HTML specified: **Color Names**, **RGB**, **HEX** or with **HSL** values. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.InactiveColor"> - <summary> - Sets the `style` of the `background-color` when tab is not the Active tab. Use HTML specified: **Color Names**, **RGB**, **HEX** or with **HSL** values. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.HoverColor"> - <summary> - Sets the `style` of the `background-color` when button is hovered over with mouse. Use HTML specified: **Color Names**, **RGB**, **HEX** or with **HSL** values. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.TabItemsHeight"> - <summary> - Sets all <see cref="T:Majorsoft.Blazor.Components.Tabs.TabItem"/> elements height in `px`. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.TabItemsWidth"> - <summary> - Sets all <see cref="T:Majorsoft.Blazor.Components.Tabs.TabItem"/> elements Width in `px`. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.Disabled"> - <summary> - Determines whether all the rendered HTML elements should be disabled or not. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.Animate"> - <summary> - Determines to apply CSS animation and transion on Tab changes or not. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.TabPositon"> - <summary> - Determines TabItems vertical positon {Left, Center, Right } - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.TabCount"> - <summary> - Returns the number of <see cref="T:Majorsoft.Blazor.Components.Tabs.TabItem"/> int the given `TabsPanel`. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.Tabs"> - <summary> - Returns all the <see cref="T:Majorsoft.Blazor.Components.Tabs.TabItem"/> reference added to the group. It can be used for activating any of the tabs. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.ActiveTab"> - <summary> - Returns currently active <see cref="T:Majorsoft.Blazor.Components.Tabs.TabItem"/> element ref also can be used to set which Tab should be active "selected". - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.OnTabChanged"> - <summary> - Callback function called when other tab activated. Active TabItem is the callback parameter. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Tabs.TabsPanel.AllOtherAttributes"> - <summary> - Blazor capture for any unmatched HTML attributes. - </summary> - </member> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor index 1a21b5b9..2f99a9fa 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor @@ -231,7 +231,7 @@ } private async Task AcceptDialog() { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog custom content action: 'Ok' Button click: '{nameof(AcceptDialog)}'."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog custom content action: 'Ok' Button click: '{nameof(AcceptDialog)}'."); if(_dialog.IsOpen) { @@ -245,7 +245,7 @@ } private async Task CancelDialog() { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog custom content action: 'Cancel' Button click: '{nameof(CancelDialog)}'."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog custom content action: 'Cancel' Button click: '{nameof(CancelDialog)}'."); _yourName = "Cancel clicked You have canceled the Dialog"; await _dialog.Close(); @@ -254,38 +254,38 @@ //Dialog events public async Task OnOpen() { - _yourName = null; - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnOpen)}' Dialog opened."); + _yourName = string.Empty; + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnOpen)}' Dialog opened."); await Task.Delay(500); - _modal1Log = WriteLog(_modal1Log, $"--------------- End of event handling ---------------"); + _modal1Log = await WriteLog(_modal1Log, $"--------------- End of event handling ---------------"); } public async Task OnClose() { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnClose)}' Dialog closed."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnClose)}' Dialog closed."); await Task.Delay(500); - _modal1Log = WriteLog(_modal1Log, $"--------------- End of event handling ---------------"); + _modal1Log = await WriteLog(_modal1Log, $"--------------- End of event handling ---------------"); } private async Task OnCloseButtonClicked(MouseEventArgs e) { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnCloseButtonClicked)}', Event args: {e}."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnCloseButtonClicked)}', Event args: {e}."); } private async Task OnOverlayClicked(MouseEventArgs e) { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnOverlayClicked)}', Event args: {e}."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnOverlayClicked)}', Event args: {e}."); } private async Task OnEscapeKeyPress(KeyboardEventArgs e) { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnEscapeKeyPress)}', Event args: {e}."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnEscapeKeyPress)}', Event args: {e}."); } private async Task OnTransitionEnded(TransitionEventArgs[] e) { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnTransitionEnded)}', Event args: {e}."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnTransitionEnded)}', Event args: {e}."); } - private string WriteLog(string log, string message) + private async Task<string> WriteLog(string log, string message) { log += $"{DateTime.Now.TimeOfDay}: {message}. \r\n"; - log1.ScrollToEndAsync(); + await log1.ScrollToEndAsync(); return log; } diff --git a/src/Majorsoft.Blazor.Components.Timer/Blazor.Components.Timer.xml b/src/Majorsoft.Blazor.Components.Timer/Blazor.Components.Timer.xml deleted file mode 100644 index 481adbfc..00000000 --- a/src/Majorsoft.Blazor.Components.Timer/Blazor.Components.Timer.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.Timer</name> - </assembly> - <members> - <member name="T:Majorsoft.Blazor.Components.Timer.Times"> - <summary> - Times to occur of <see cref="T:Majorsoft.Blazor.Components.Timer.AdvancedTimer"/> ticks - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Timer.Times.Count"> - <summary> - Occurrence count - </summary> - </member> - <member name="M:Majorsoft.Blazor.Components.Timer.Times.Once"> - <summary> - Should occur only once - </summary> - <returns></returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Timer.Times.Infinite"> - <summary> - Should occur until stopped - </summary> - <returns></returns> - </member> - <member name="M:Majorsoft.Blazor.Components.Timer.Times.Exactly(System.UInt64)"> - <summary> - Should occur exactly to the given number of times - </summary> - <param name="count">N occurrence</param> - <returns></returns> - </member> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Components.Toggle/Blazor.Components.Toggle.xml b/src/Majorsoft.Blazor.Components.Toggle/Blazor.Components.Toggle.xml deleted file mode 100644 index bddb33bf..00000000 --- a/src/Majorsoft.Blazor.Components.Toggle/Blazor.Components.Toggle.xml +++ /dev/null @@ -1,178 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.Toggle</name> - </assembly> - <members> - <member name="T:Majorsoft.Blazor.Components.Toggle.ToggleSwitchStyle"> - <summary> - Toggle switch handle style - </summary> - </member> - <member name="F:Majorsoft.Blazor.Components.Toggle.ToggleSwitchStyle.Ellipse"> - <summary> - Ellipse handle style - </summary> - </member> - <member name="F:Majorsoft.Blazor.Components.Toggle.ToggleSwitchStyle.Circle"> - <summary> - Circle handle style - </summary> - </member> - <member name="F:Majorsoft.Blazor.Components.Toggle.ToggleSwitchStyle.Square"> - <summary> - Square handle style - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButton.InnerElementReference"> - <summary> - Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButton.OnColor"> - <summary> - Sets the `style` of the `background-color` when button is ON (bool value `true`). Use HTML specified: **Color Names**, **RGB**, **HEX** or with **HSL** values. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButton.OffColor"> - <summary> - Sets the `style` of the `background-color` when button is OFF (bool value `false`). Use HTML specified: **Color Names**, **RGB**, **HEX** or with **HSL** values. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButton.HoverColor"> - <summary> - Sets the `style` of the `background-color` when button is hovered over with mouse. Use HTML specified: **Color Names**, **RGB**, **HEX** or with **HSL** values. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButton.Content"> - <summary> - Required HTML content to show in Toggle button. It can be any text or image (please use transparent background image). - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButton.Checked"> - <summary> - Represents Toggle button value: **ON**: `true`, **OFF**: `false`. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButton.Height"> - <summary> - HTML element height in `px`. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButton.Width"> - <summary> - HTML element Width in `px`. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButton.Disabled"> - <summary> - Determines whether the rendered HTML element should be disabled or not. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButton.OnToggleChanged"> - <summary> - Callback function called when component toggled. Actual toggle `Value` is the callback `bool` parameter. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButton.AllOtherAttributes"> - <summary> - Blazor capture for any unmatched HTML attributes. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButtonGroup.InnerElementReference"> - <summary> - Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButtonGroup.ToggleButtons"> - <summary> - Required list of ToggleButtons components. See usage example. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButtonGroup.Disabled"> - <summary> - Determines whether all the rendered HTML elements should be disabled or not. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButtonGroup.MustToggled"> - <summary> - Determines whether at least one <see cref="T:Majorsoft.Blazor.Components.Toggle.ToggleButton"/> must be toggled all the time or all buttons can be Off. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButtonGroup.OnToggleChanged"> - <summary> - Callback function called when component toggled. Actual toggled (selected) button is the callback parameter. When nothing selected value is `NULL`. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButtonGroup.ActiveButton"> - <summary> - Returns currently active "toggled" <see cref="T:Majorsoft.Blazor.Components.Toggle.ToggleButton"/> element ref also can be used to set which button should be active "toggled". - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButtonGroup.ButtonCount"> - <summary> - Returns the number of <see cref="T:Majorsoft.Blazor.Components.Toggle.ToggleButton"/> int the given `ToggleButtonGroup`. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButtonGroup.Buttons"> - <summary> - Returns all the <see cref="T:Majorsoft.Blazor.Components.Toggle.ToggleButton"/> reference added to the group. It can be used for activating any of the elements. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleButtonGroup.AllOtherAttributes"> - <summary> - Blazor capture for any unmatched HTML attributes. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleSwitch.InnerElementReference"> - <summary> - Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleSwitch.OnColor"> - <summary> - Sets the `background-color` when switch is ON (bool value `true`). Use HTML specified: **Color Names**, **RGB**, **HEX** or with **HSL** values. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleSwitch.OffColor"> - <summary> - Sets the `background-color` when switch is OFF (bool value `false`). Use HTML specified: **Color Names**, **RGB**, **HEX** or with **HSL** values. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleSwitch.Checked"> - <summary> - Represents Toggle switch value: **ON**: `true`, **OFF**: `false` - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleSwitch.Height"> - <summary> - HTML element height in `px`. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleSwitch.Width"> - <summary> - HTML element Width in `px`. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleSwitch.Disabled"> - <summary> - Determines whether the rendered HTML element should be disabled or not. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleSwitch.HandleStyle"> - <summary> - Renders Toggle switch handle with different shaped styles <see cref="T:Majorsoft.Blazor.Components.Toggle.ToggleSwitchStyle"/> - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleSwitch.OnToggleChanged"> - <summary> - Callback function called when component toggled. Actual toggle `Value` is the callback `bool` parameter. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Toggle.ToggleSwitch.AllOtherAttributes"> - <summary> - Blazor capture for any unmatched HTML attributes. - </summary> - </member> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Components.Typeahead/Blazor.Components.Typeahead.xml b/src/Majorsoft.Blazor.Components.Typeahead/Blazor.Components.Typeahead.xml deleted file mode 100644 index 5043ae74..00000000 --- a/src/Majorsoft.Blazor.Components.Typeahead/Blazor.Components.Typeahead.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0"?> -<doc> - <assembly> - <name>Majorsoft.Blazor.Components.Typeahead</name> - </assembly> - <members> - </members> -</doc> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs index 2b453ada..447dca18 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs @@ -31,7 +31,7 @@ public interface IGoogleAnalyticsService : IAsyncDisposable /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> /// <param name="configInfo">Is one or more optional parameter-value pairs</param> /// <returns>Async ValueTask</returns> - ValueTask Config(string trackingId = "", Dictionary<string, object> configInfo = null); + ValueTask Config(string trackingId = "", Dictionary<string, object>? configInfo = null); /// <summary> /// Allows you to get various values from gtag.js including values set with the set command. @@ -46,7 +46,7 @@ public interface IGoogleAnalyticsService : IAsyncDisposable /// </summary> /// <param name="parameterValuePair">Is a key name and the value that is to persist across gtag() calls.</param> /// <returns>Async ValueTask</returns> - ValueTask Set(Dictionary<string, object> parameterValuePair = null); + ValueTask Set(Dictionary<string, object>? parameterValuePair = null); /// <summary> /// Use the event command to send event data. @@ -91,7 +91,7 @@ public async ValueTask Initialize(string trackingId) await module.InvokeVoidAsync("init", trackingId); } - public async ValueTask Config(string trackingId = "", Dictionary<string, object> configInfo = null) + public async ValueTask Config(string trackingId = "", Dictionary<string, object>? configInfo = null) { var module = await moduleTask.Value; await module.InvokeVoidAsync("config", string.IsNullOrWhiteSpace(trackingId) ? TrackingId : trackingId, configInfo?.ToList()); @@ -101,7 +101,7 @@ public async ValueTask Get(string fieldName, string trackingId = "") var module = await moduleTask.Value; await module.InvokeVoidAsync("get", string.IsNullOrWhiteSpace(trackingId) ? TrackingId : trackingId, fieldName); //TODO: callback results } - public async ValueTask Set(Dictionary<string, object> parameterValuePair = null) + public async ValueTask Set(Dictionary<string, object>? parameterValuePair = null) { var module = await moduleTask.Value; await module.InvokeVoidAsync("set", parameterValuePair?.ToList()); From bf5c44f1f83fbde8834fffb7cc84e58de5edca26 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Wed, 9 Jun 2021 20:28:15 +0200 Subject: [PATCH 23/69] bUnit package updates. --- .../Majorsoft.Blazor.Components.Debounce.Tests.csproj | 4 ++-- .../Majorsoft.Blazor.Components.Loading.Tests.csproj | 4 ++-- .../Majorsoft.Blazor.Components.Maps.Tests.csproj | 4 ++-- .../Majorsoft.Blazor.Components.Modal.Tests.csproj | 4 ++-- .../Majorsoft.Blazor.Components.PermaLink.Tests.csproj | 4 ++-- .../Majorsoft.Blazor.Components.Timer.Tests.csproj | 4 ++-- .../Majorsoft.Blazor.Components.Toggle.Tests.csproj | 4 ++-- .../Majorsoft.Blazor.Components.Typeahead.Tests.csproj | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Debounce.Tests/Majorsoft.Blazor.Components.Debounce.Tests.csproj b/src/Majorsoft.Blazor.Components.Debounce.Tests/Majorsoft.Blazor.Components.Debounce.Tests.csproj index 7a2549bb..f15b2339 100644 --- a/src/Majorsoft.Blazor.Components.Debounce.Tests/Majorsoft.Blazor.Components.Debounce.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Debounce.Tests/Majorsoft.Blazor.Components.Debounce.Tests.csproj @@ -7,8 +7,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="bunit" Version="1.0.0-beta-11" /> - <PackageReference Include="bunit.web" Version="1.0.0-beta-11" /> + <PackageReference Include="bunit" Version="1.1.5" /> + <PackageReference Include="bunit.web" Version="1.1.5" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> diff --git a/src/Majorsoft.Blazor.Components.Loading.Tests/Majorsoft.Blazor.Components.Loading.Tests.csproj b/src/Majorsoft.Blazor.Components.Loading.Tests/Majorsoft.Blazor.Components.Loading.Tests.csproj index db151b67..bcd413eb 100644 --- a/src/Majorsoft.Blazor.Components.Loading.Tests/Majorsoft.Blazor.Components.Loading.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Loading.Tests/Majorsoft.Blazor.Components.Loading.Tests.csproj @@ -7,8 +7,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="bunit" Version="1.0.0-beta-11" /> - <PackageReference Include="bunit.web" Version="1.0.0-beta-11" /> + <PackageReference Include="bunit" Version="1.1.5" /> + <PackageReference Include="bunit.web" Version="1.1.5" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> diff --git a/src/Majorsoft.Blazor.Components.Maps.Tests/Majorsoft.Blazor.Components.Maps.Tests.csproj b/src/Majorsoft.Blazor.Components.Maps.Tests/Majorsoft.Blazor.Components.Maps.Tests.csproj index 0ca03c87..b09a479c 100644 --- a/src/Majorsoft.Blazor.Components.Maps.Tests/Majorsoft.Blazor.Components.Maps.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Maps.Tests/Majorsoft.Blazor.Components.Maps.Tests.csproj @@ -7,8 +7,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="bunit" Version="1.0.0-beta-11" /> - <PackageReference Include="bunit.web" Version="1.0.0-beta-11" /> + <PackageReference Include="bunit" Version="1.1.5" /> + <PackageReference Include="bunit.web" Version="1.1.5" /> <PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> diff --git a/src/Majorsoft.Blazor.Components.Modal.Tests/Majorsoft.Blazor.Components.Modal.Tests.csproj b/src/Majorsoft.Blazor.Components.Modal.Tests/Majorsoft.Blazor.Components.Modal.Tests.csproj index 17fe0067..007afc46 100644 --- a/src/Majorsoft.Blazor.Components.Modal.Tests/Majorsoft.Blazor.Components.Modal.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Modal.Tests/Majorsoft.Blazor.Components.Modal.Tests.csproj @@ -7,8 +7,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="bunit" Version="1.0.0-beta-11" /> - <PackageReference Include="bunit.web" Version="1.0.0-beta-11" /> + <PackageReference Include="bunit" Version="1.1.5" /> + <PackageReference Include="bunit.web" Version="1.1.5" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> diff --git a/src/Majorsoft.Blazor.Components.PermaLink.Tests/Majorsoft.Blazor.Components.PermaLink.Tests.csproj b/src/Majorsoft.Blazor.Components.PermaLink.Tests/Majorsoft.Blazor.Components.PermaLink.Tests.csproj index 4c541299..1e42be5a 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink.Tests/Majorsoft.Blazor.Components.PermaLink.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.PermaLink.Tests/Majorsoft.Blazor.Components.PermaLink.Tests.csproj @@ -7,8 +7,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="bunit" Version="1.0.0-beta-11" /> - <PackageReference Include="bunit.web" Version="1.0.0-beta-11" /> + <PackageReference Include="bunit" Version="1.1.5" /> + <PackageReference Include="bunit.web" Version="1.1.5" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> diff --git a/src/Majorsoft.Blazor.Components.Timer.Tests/Majorsoft.Blazor.Components.Timer.Tests.csproj b/src/Majorsoft.Blazor.Components.Timer.Tests/Majorsoft.Blazor.Components.Timer.Tests.csproj index bdc3c80e..4789a143 100644 --- a/src/Majorsoft.Blazor.Components.Timer.Tests/Majorsoft.Blazor.Components.Timer.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Timer.Tests/Majorsoft.Blazor.Components.Timer.Tests.csproj @@ -7,8 +7,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="bunit" Version="1.0.0-beta-11" /> - <PackageReference Include="bunit.web" Version="1.0.0-beta-11" /> + <PackageReference Include="bunit" Version="1.1.5" /> + <PackageReference Include="bunit.web" Version="1.1.5" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> diff --git a/src/Majorsoft.Blazor.Components.Toggle.Tests/Majorsoft.Blazor.Components.Toggle.Tests.csproj b/src/Majorsoft.Blazor.Components.Toggle.Tests/Majorsoft.Blazor.Components.Toggle.Tests.csproj index 9fddb720..ba7076ea 100644 --- a/src/Majorsoft.Blazor.Components.Toggle.Tests/Majorsoft.Blazor.Components.Toggle.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Toggle.Tests/Majorsoft.Blazor.Components.Toggle.Tests.csproj @@ -7,8 +7,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="bunit" Version="1.0.0-beta-11" /> - <PackageReference Include="bunit.web" Version="1.0.0-beta-11" /> + <PackageReference Include="bunit" Version="1.1.5" /> + <PackageReference Include="bunit.web" Version="1.1.5" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> diff --git a/src/Majorsoft.Blazor.Components.Typeahead.Tests/Majorsoft.Blazor.Components.Typeahead.Tests.csproj b/src/Majorsoft.Blazor.Components.Typeahead.Tests/Majorsoft.Blazor.Components.Typeahead.Tests.csproj index a718d8b5..b494e437 100644 --- a/src/Majorsoft.Blazor.Components.Typeahead.Tests/Majorsoft.Blazor.Components.Typeahead.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Typeahead.Tests/Majorsoft.Blazor.Components.Typeahead.Tests.csproj @@ -7,8 +7,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="bunit" Version="1.0.0-beta-11" /> - <PackageReference Include="bunit.web" Version="1.0.0-beta-11" /> + <PackageReference Include="bunit" Version="1.1.5" /> + <PackageReference Include="bunit.web" Version="1.1.5" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> From ea53da7c4371f265ed44ef8a9e35fcc33cd8b422 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Wed, 9 Jun 2021 20:34:05 +0200 Subject: [PATCH 24/69] Updated Unit test nuget packages. --- .../Majorsoft.Blazor.Components.Core.Tests.csproj | 6 +++--- .../Majorsoft.Blazor.Components.Debounce.Tests.csproj | 6 +++--- .../Majorsoft.Blazor.Components.Loading.Tests.csproj | 6 +++--- .../Majorsoft.Blazor.Components.Maps.Tests.csproj | 6 +++--- .../Majorsoft.Blazor.Components.Modal.Tests.csproj | 6 +++--- .../Majorsoft.Blazor.Components.PermaLink.Tests.csproj | 6 +++--- .../Majorsoft.Blazor.Components.Timer.Tests.csproj | 6 +++--- .../Majorsoft.Blazor.Components.Toggle.Tests.csproj | 6 +++--- .../Majorsoft.Blazor.Components.Typeahead.Tests.csproj | 6 +++--- 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Core.Tests/Majorsoft.Blazor.Components.Core.Tests.csproj b/src/Majorsoft.Blazor.Components.Core.Tests/Majorsoft.Blazor.Components.Core.Tests.csproj index 100b5107..d5203afc 100644 --- a/src/Majorsoft.Blazor.Components.Core.Tests/Majorsoft.Blazor.Components.Core.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Core.Tests/Majorsoft.Blazor.Components.Core.Tests.csproj @@ -6,10 +6,10 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" /> <PackageReference Include="Moq" Version="4.16.1" /> - <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> - <PackageReference Include="MSTest.TestFramework" Version="2.2.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.4" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.4" /> </ItemGroup> <ItemGroup> diff --git a/src/Majorsoft.Blazor.Components.Debounce.Tests/Majorsoft.Blazor.Components.Debounce.Tests.csproj b/src/Majorsoft.Blazor.Components.Debounce.Tests/Majorsoft.Blazor.Components.Debounce.Tests.csproj index f15b2339..019ad854 100644 --- a/src/Majorsoft.Blazor.Components.Debounce.Tests/Majorsoft.Blazor.Components.Debounce.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Debounce.Tests/Majorsoft.Blazor.Components.Debounce.Tests.csproj @@ -9,10 +9,10 @@ <ItemGroup> <PackageReference Include="bunit" Version="1.1.5" /> <PackageReference Include="bunit.web" Version="1.1.5" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" /> <PackageReference Include="Moq" Version="4.16.1" /> - <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> - <PackageReference Include="MSTest.TestFramework" Version="2.2.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.4" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.4" /> <PackageReference Include="coverlet.collector" Version="3.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> diff --git a/src/Majorsoft.Blazor.Components.Loading.Tests/Majorsoft.Blazor.Components.Loading.Tests.csproj b/src/Majorsoft.Blazor.Components.Loading.Tests/Majorsoft.Blazor.Components.Loading.Tests.csproj index bcd413eb..9cdf2119 100644 --- a/src/Majorsoft.Blazor.Components.Loading.Tests/Majorsoft.Blazor.Components.Loading.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Loading.Tests/Majorsoft.Blazor.Components.Loading.Tests.csproj @@ -9,10 +9,10 @@ <ItemGroup> <PackageReference Include="bunit" Version="1.1.5" /> <PackageReference Include="bunit.web" Version="1.1.5" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" /> <PackageReference Include="Moq" Version="4.16.1" /> - <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> - <PackageReference Include="MSTest.TestFramework" Version="2.2.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.4" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.4" /> <PackageReference Include="coverlet.collector" Version="3.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> diff --git a/src/Majorsoft.Blazor.Components.Maps.Tests/Majorsoft.Blazor.Components.Maps.Tests.csproj b/src/Majorsoft.Blazor.Components.Maps.Tests/Majorsoft.Blazor.Components.Maps.Tests.csproj index b09a479c..29f9f990 100644 --- a/src/Majorsoft.Blazor.Components.Maps.Tests/Majorsoft.Blazor.Components.Maps.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Maps.Tests/Majorsoft.Blazor.Components.Maps.Tests.csproj @@ -10,9 +10,9 @@ <PackageReference Include="bunit" Version="1.1.5" /> <PackageReference Include="bunit.web" Version="1.1.5" /> <PackageReference Include="Moq" Version="4.16.1" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> - <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> - <PackageReference Include="MSTest.TestFramework" Version="2.2.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.4" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.4" /> <PackageReference Include="coverlet.collector" Version="3.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> diff --git a/src/Majorsoft.Blazor.Components.Modal.Tests/Majorsoft.Blazor.Components.Modal.Tests.csproj b/src/Majorsoft.Blazor.Components.Modal.Tests/Majorsoft.Blazor.Components.Modal.Tests.csproj index 007afc46..b1de6b05 100644 --- a/src/Majorsoft.Blazor.Components.Modal.Tests/Majorsoft.Blazor.Components.Modal.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Modal.Tests/Majorsoft.Blazor.Components.Modal.Tests.csproj @@ -9,10 +9,10 @@ <ItemGroup> <PackageReference Include="bunit" Version="1.1.5" /> <PackageReference Include="bunit.web" Version="1.1.5" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" /> <PackageReference Include="Moq" Version="4.16.1" /> - <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> - <PackageReference Include="MSTest.TestFramework" Version="2.2.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.4" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.4" /> <PackageReference Include="coverlet.collector" Version="3.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> diff --git a/src/Majorsoft.Blazor.Components.PermaLink.Tests/Majorsoft.Blazor.Components.PermaLink.Tests.csproj b/src/Majorsoft.Blazor.Components.PermaLink.Tests/Majorsoft.Blazor.Components.PermaLink.Tests.csproj index 1e42be5a..4ca08060 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink.Tests/Majorsoft.Blazor.Components.PermaLink.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.PermaLink.Tests/Majorsoft.Blazor.Components.PermaLink.Tests.csproj @@ -9,10 +9,10 @@ <ItemGroup> <PackageReference Include="bunit" Version="1.1.5" /> <PackageReference Include="bunit.web" Version="1.1.5" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" /> <PackageReference Include="Moq" Version="4.16.1" /> - <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> - <PackageReference Include="MSTest.TestFramework" Version="2.2.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.4" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.4" /> <PackageReference Include="coverlet.collector" Version="3.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> diff --git a/src/Majorsoft.Blazor.Components.Timer.Tests/Majorsoft.Blazor.Components.Timer.Tests.csproj b/src/Majorsoft.Blazor.Components.Timer.Tests/Majorsoft.Blazor.Components.Timer.Tests.csproj index 4789a143..33af22a0 100644 --- a/src/Majorsoft.Blazor.Components.Timer.Tests/Majorsoft.Blazor.Components.Timer.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Timer.Tests/Majorsoft.Blazor.Components.Timer.Tests.csproj @@ -9,10 +9,10 @@ <ItemGroup> <PackageReference Include="bunit" Version="1.1.5" /> <PackageReference Include="bunit.web" Version="1.1.5" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" /> <PackageReference Include="Moq" Version="4.16.1" /> - <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> - <PackageReference Include="MSTest.TestFramework" Version="2.2.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.4" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.4" /> <PackageReference Include="coverlet.collector" Version="3.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> diff --git a/src/Majorsoft.Blazor.Components.Toggle.Tests/Majorsoft.Blazor.Components.Toggle.Tests.csproj b/src/Majorsoft.Blazor.Components.Toggle.Tests/Majorsoft.Blazor.Components.Toggle.Tests.csproj index ba7076ea..a39c7e98 100644 --- a/src/Majorsoft.Blazor.Components.Toggle.Tests/Majorsoft.Blazor.Components.Toggle.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Toggle.Tests/Majorsoft.Blazor.Components.Toggle.Tests.csproj @@ -9,10 +9,10 @@ <ItemGroup> <PackageReference Include="bunit" Version="1.1.5" /> <PackageReference Include="bunit.web" Version="1.1.5" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" /> <PackageReference Include="Moq" Version="4.16.1" /> - <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> - <PackageReference Include="MSTest.TestFramework" Version="2.2.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.4" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.4" /> <PackageReference Include="coverlet.collector" Version="3.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> diff --git a/src/Majorsoft.Blazor.Components.Typeahead.Tests/Majorsoft.Blazor.Components.Typeahead.Tests.csproj b/src/Majorsoft.Blazor.Components.Typeahead.Tests/Majorsoft.Blazor.Components.Typeahead.Tests.csproj index b494e437..5786ce36 100644 --- a/src/Majorsoft.Blazor.Components.Typeahead.Tests/Majorsoft.Blazor.Components.Typeahead.Tests.csproj +++ b/src/Majorsoft.Blazor.Components.Typeahead.Tests/Majorsoft.Blazor.Components.Typeahead.Tests.csproj @@ -9,10 +9,10 @@ <ItemGroup> <PackageReference Include="bunit" Version="1.1.5" /> <PackageReference Include="bunit.web" Version="1.1.5" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" /> <PackageReference Include="Moq" Version="4.16.1" /> - <PackageReference Include="MSTest.TestAdapter" Version="2.2.1" /> - <PackageReference Include="MSTest.TestFramework" Version="2.2.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.4" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.4" /> <PackageReference Include="coverlet.collector" Version="3.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> From 54d967229dcaae5a0a3a3b1c946f3c253a9755c6 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Thu, 10 Jun 2021 19:24:39 +0200 Subject: [PATCH 25/69] Unit test PermaLink element added XML comments. --- .../LoadingElement.razor | 3 + .../LoadingPage.razor | 3 + .../Majorsoft.Blazor.Components.Loading.xml | 10 ++ .../PermaLinkElementTest.cs | 154 ++++++++++++++++-- .../Majorsoft.Blazor.Components.PermaLink.xml | 57 +++++++ .../PermaLinkElement.razor | 37 +++++ 6 files changed, 248 insertions(+), 16 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Loading/LoadingElement.razor b/src/Majorsoft.Blazor.Components.Loading/LoadingElement.razor index 385b7e3d..e6eb3c36 100644 --- a/src/Majorsoft.Blazor.Components.Loading/LoadingElement.razor +++ b/src/Majorsoft.Blazor.Components.Loading/LoadingElement.razor @@ -66,6 +66,9 @@ else /// </summary> [Parameter] public EventCallback OnLoading { get; set; } + /// <summary> + /// Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. + /// </summary> [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> AllOtherAttributes { get; set; } diff --git a/src/Majorsoft.Blazor.Components.Loading/LoadingPage.razor b/src/Majorsoft.Blazor.Components.Loading/LoadingPage.razor index d870e970..38dc0c53 100644 --- a/src/Majorsoft.Blazor.Components.Loading/LoadingPage.razor +++ b/src/Majorsoft.Blazor.Components.Loading/LoadingPage.razor @@ -57,6 +57,9 @@ /// </summary> [Parameter] public EventCallback OnLoading { get; set; } + /// <summary> + /// Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. + /// </summary> [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> AllOtherAttributes { get; set; } diff --git a/src/Majorsoft.Blazor.Components.Loading/Majorsoft.Blazor.Components.Loading.xml b/src/Majorsoft.Blazor.Components.Loading/Majorsoft.Blazor.Components.Loading.xml index 7027ed22..8be30633 100644 --- a/src/Majorsoft.Blazor.Components.Loading/Majorsoft.Blazor.Components.Loading.xml +++ b/src/Majorsoft.Blazor.Components.Loading/Majorsoft.Blazor.Components.Loading.xml @@ -75,6 +75,11 @@ Callback function called when component OnInitializedAsync Blazor event triggered. </summary> </member> + <member name="P:Majorsoft.Blazor.Components.Loading.LoadingElement.AllOtherAttributes"> + <summary> + Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. + </summary> + </member> <member name="P:Majorsoft.Blazor.Components.Loading.LoadingElement.IsLoading"> <summary> Can be set overlay to loading state true or remove it false . Returns true if loading overlay is prompted, otherwise false. @@ -101,6 +106,11 @@ Callback function called when component OnInitializedAsync Blazor event triggered. </summary> </member> + <member name="P:Majorsoft.Blazor.Components.Loading.LoadingPage.AllOtherAttributes"> + <summary> + Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. + </summary> + </member> <member name="P:Majorsoft.Blazor.Components.Loading.LoadingPage.IsLoading"> <summary> Can be set overlay to loading state true or remove it false . Returns true if loading overlay is prompted, otherwise false. diff --git a/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs index 1d7d2d0e..464e7511 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs +++ b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs @@ -1,17 +1,13 @@ -using System; -using System.Threading.Tasks; - using Majorsoft.Blazor.Components.Common.JsInterop.Clipboard; +using Majorsoft.Blazor.Components.Common.JsInterop.ElementInfo; using Bunit; -using Bunit.TestDoubles; - -using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using System; namespace Majorsoft.Blazor.Components.PermaLink.Tests { @@ -20,6 +16,7 @@ public class PermaLinkElementTest { private Bunit.TestContext _testContext; private Mock<IClipboardHandler> _clipboardJsMock; + private BunitJSModuleInterop _jsInteropModul; [TestInitialize] public void Init() @@ -29,7 +26,10 @@ public void Init() var mock = new Mock<ILogger<PermaLinkElement>>(); _clipboardJsMock = new Mock<IClipboardHandler>(); - _testContext.Services.AddMockJSRuntime(JSRuntimeMockMode.Strict); + _testContext.JSInterop.Mode = JSRuntimeMode.Strict; + _jsInteropModul = _testContext.JSInterop.SetupModule("./_content/Majorsoft.Blazor.Components.Common.JsInterop/elementInfo.js"); + _jsInteropModul.Setup<DomRect>("getBoundingClientRect", _ => true).SetResult(new DomRect()); + _testContext.Services.Add(new ServiceDescriptor(typeof(ILogger<PermaLinkElement>), mock.Object)); _testContext.Services.Add(new ServiceDescriptor(typeof(IClipboardHandler), _clipboardJsMock.Object)); } @@ -43,18 +43,140 @@ public void Cleanup() [TestMethod] public void PermaLinkElement_should_rendered_correctly_html_attributes() { - //Test does not work unit new bUnit version - //https://github.com/egil/bUnit/issues/231 + var rendered = _testContext.RenderComponent<PermaLinkElement>( + ("title", "Test"), //HTML attributes + ("style", "style") //HTML attributes + ); + + var input = rendered.Find("div"); + + Assert.IsNotNull(input); + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight"" title=""Test"" style=""style""><a></a></div>"); + } + + [TestMethod] + public void PermaLinkElement_should_rendered_Content_correctly() + { + var rendered = _testContext.RenderComponent<PermaLinkElement>(parameters => parameters + .Add(p => p.Content, "<h2>Hower over</h2>")); + + var input = rendered.Find("div"); + + Assert.IsNotNull(input); + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight""><a></a> <h2>Hower over</h2> </div>"); + } + + [TestMethod] + public void PermaLinkElement_should_rendered_PermaLinkName_correctly() + { + var rendered = _testContext.RenderComponent<PermaLinkElement>(parameters => parameters + .Add(p => p.PermaLinkName, "#linkName")); + + var input = rendered.Find("div"); + + Assert.IsNotNull(input); + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight""><a name=""#linkName""></a> </div>"); + } + + [TestMethod] + public void PermaLinkElement_should_rendered_IconMarginTop_correctly() + { + var rendered = _testContext.RenderComponent<PermaLinkElement>(parameters => parameters + .Add(p => p.ShowIcon, ShowPermaLinkIcon.Always) + .Add(p => p.IconMarginTop, 8)); + + var input = rendered.Find("div"); + + Assert.IsNotNull(input); + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight""><a></a> <img style=""margin-top: 8px;"" width=""16"" height=""16"" class=""permaLinkIcon"" src=""_content/Majorsoft.Blazor.Components.PermaLink/link2.svg""> </div>"); + } + + [TestMethod] + public void PermaLinkElement_should_rendered_IconSize_correctly() + { + var rendered = _testContext.RenderComponent<PermaLinkElement>(parameters => parameters + .Add(p => p.ShowIcon, ShowPermaLinkIcon.Always) + .Add(p => p.IconSize, 34)); + + var input = rendered.Find("div"); + + Assert.IsNotNull(input); + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight""><a></a> <img style=""margin-top: 0px;"" width=""34"" height=""34"" class=""permaLinkIcon"" src=""_content/Majorsoft.Blazor.Components.PermaLink/link2.svg""> </div>"); + } + + [TestMethod] + public void PermaLinkElement_should_rendered_IconPosition_correctly() + { + foreach (var item in Enum.GetValues<PermaLinkIconPosition>()) + { + var rendered = _testContext.RenderComponent<PermaLinkElement>(parameters => parameters + .Add(p => p.ShowIcon, ShowPermaLinkIcon.Always) + .Add(p => p.IconPosition, item)); + + //rendered.SetParametersAndRender(parameters => parameters.Add(p => p.IconPosition, item)); + var input = rendered.Find("div"); + + Assert.IsNotNull(input); + + var imgStyle = item == PermaLinkIconPosition.Left ? "left: 0px; position: absolute;" : "margin-top: 0px;"; + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDiv{item}""><a></a> <img style=""{imgStyle} margin-top: 0px;"" width=""16"" height=""16"" class=""permaLinkIcon"" src=""_content/Majorsoft.Blazor.Components.PermaLink/link2.svg""> </div>"); + } + } + + [TestMethod] + public void PermaLinkElement_should_rendered_IconStyle_correctly() + { + foreach (var item in Enum.GetValues<PermaLinkStyle>()) + { + var rendered = _testContext.RenderComponent<PermaLinkElement>(parameters => parameters + .Add(p => p.ShowIcon, ShowPermaLinkIcon.Always) + .Add(p => p.IconStyle, item)); + + var input = rendered.Find("div"); + + Assert.IsNotNull(input); + + var icon = item == PermaLinkStyle.Normal ? "link2.svg" : "link.svg"; + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight""><a></a> <img style=""margin-top: 0px;"" width=""16"" height=""16"" class=""permaLinkIcon"" src=""_content/Majorsoft.Blazor.Components.PermaLink/{icon}""> </div>"); + } + } + + [TestMethod] + public void PermaLinkElement_should_rendered_ShowPermaLinkIcon_No_correctly() + { + var rendered = _testContext.RenderComponent<PermaLinkElement>(parameters => parameters + .Add(p => p.ShowIcon, ShowPermaLinkIcon.No)); + + var input = rendered.Find("div"); + + Assert.IsNotNull(input); + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""""><a></a> </div>"); + } + [TestMethod] + public void PermaLinkElement_should_rendered_ShowPermaLinkIcon_Always_correctly() + { + var rendered = _testContext.RenderComponent<PermaLinkElement>(parameters => parameters + .Add(p => p.ShowIcon, ShowPermaLinkIcon.Always)); + + var input = rendered.Find("div"); + + Assert.IsNotNull(input); + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight""><a></a> <img style=""margin-top: 0px;"" width=""16"" height=""16"" class=""permaLinkIcon"" src=""_content/Majorsoft.Blazor.Components.PermaLink/link2.svg""> </div>"); + } + [TestMethod] + public void PermaLinkElement_should_rendered_ShowPermaLinkIcon_OnHover_correctly() + { + var rendered = _testContext.RenderComponent<PermaLinkElement>(parameters => parameters + .Add(p => p.ShowIcon, ShowPermaLinkIcon.OnHover)); + + var input = rendered.Find("div"); - //var rendered = _testContext.RenderComponent<PermaLinkElement>( - // ("title", "t"), //HTML attributes - // ("style", "style") //HTML attributes - // ); + Assert.IsNotNull(input); + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight""><a></a> </div>"); - //var input = rendered.Find("div"); + //input.MouseOver(); - //Assert.IsNotNull(input); - //input.MarkupMatches(@"<input id=""id1"" class=""form-control w-100"" />"); + //TODO: finish testing on mouse hover cases... } } } \ No newline at end of file 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 4e6ae814..a22d2bfd 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/Majorsoft.Blazor.Components.PermaLink.xml +++ b/src/Majorsoft.Blazor.Components.PermaLink/Majorsoft.Blazor.Components.PermaLink.xml @@ -37,5 +37,62 @@ Implementation of <see cref="T:Majorsoft.Blazor.Components.PermaLink.IPermaLinkWatcherService"/> </summary> </member> + <member name="P:Majorsoft.Blazor.Components.PermaLink.PermaLinkElement.InnerElementReference"> + <summary> + Exposes a Blazor ElementReference of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.PermaLink.PermaLinkElement.PermaLinkName"> + <summary> + Value of the rendered anchor HTML tag. NOTE: it is required and will be part of the URL, do not add # use non URL friendly characters! + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.PermaLink.PermaLinkElement.Content"> + <summary> + Required HTML content to render with mouse enter/leave events to show Permalink icon. + NOTE: it can be any arbitrary HTML elemnt but should be just a header text e.g. h1-h6 element. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.PermaLink.PermaLinkElement.ShowIcon"> + <summary> + Enum value which sets how to show clickable permalink the icon. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.PermaLink.PermaLinkElement.IconPosition"> + <summary> + Enum value which sets where to show the icon. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.PermaLink.PermaLinkElement.IconMarginTop"> + <summary> + Sets the icon margin-top CSS property in px. With this icon can be centered. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.PermaLink.PermaLinkElement.IconSize"> + <summary> + Sets the icon <img width="" height="" /> values. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.PermaLink.PermaLinkElement.IconStyle"> + <summary> + Enum value which sets the displayed icon type. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.PermaLink.PermaLinkElement.IconActions"> + <summary> + FLAG Enum value which sets the behaviour of the icon click: Copy, Navigate. + Flag values can be combined: PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.PermaLink.PermaLinkElement.AdditionalAttributes"> + <summary> + Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.PermaLink.PermaLinkElement.OnPermaLinkCopied"> + <summary> + Callback function called when Permalink icon clicked and PermaLinkIconActions.Copy feature was set. + </summary> + </member> </members> </doc> diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkElement.razor b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkElement.razor index 0e4477ee..d7c57a6f 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkElement.razor +++ b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkElement.razor @@ -38,7 +38,11 @@ else if(IconPosition == PermaLinkIconPosition.Left) @code { private string _componentId = Guid.NewGuid().ToString("n"); + private ElementReference _permaDiv; + /// <summary> + /// Exposes a Blazor ElementReference of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + /// </summary> public ElementReference InnerElementReference => _permaDiv; private DomRect _rect; @@ -57,6 +61,9 @@ else if(IconPosition == PermaLinkIconPosition.Left) private bool _showIcon = false; private string? _permaLinkName; + /// <summary> + /// Value of the rendered anchor HTML tag. NOTE: it is required and will be part of the URL, do not add # use non URL friendly characters! + /// </summary> [Parameter] public string PermaLinkName { get => _permaLinkName; @@ -66,11 +73,22 @@ else if(IconPosition == PermaLinkIconPosition.Left) } } + /// <summary> + /// Required HTML content to render with mouse enter/leave events to show Permalink icon. + /// NOTE: it can be any arbitrary HTML elemnt but should be just a header text e.g. h1-h6 element. + /// </summary> [Parameter] public RenderFragment Content { get; set; } //Icon visibility, style... + /// <summary> + /// Enum value which sets how to show clickable permalink the icon. + /// </summary> [Parameter] public ShowPermaLinkIcon ShowIcon { get; set; } = ShowPermaLinkIcon.OnHover; private PermaLinkIconPosition _iconPosition = PermaLinkIconPosition.Right; + + /// <summary> + /// Enum value which sets where to show the icon. + /// </summary> [Parameter] public PermaLinkIconPosition IconPosition { get => _iconPosition; @@ -84,14 +102,33 @@ else if(IconPosition == PermaLinkIconPosition.Left) } } } + /// <summary> + /// Sets the icon margin-top CSS property in px. With this icon can be centered. + /// </summary> [Parameter] public double IconMarginTop { get; set; } = 0; + /// <summary> + /// Sets the icon <img width="" height="" /> values. + /// </summary> [Parameter] public int IconSize { get; set; } = 16; + /// <summary> + /// Enum value which sets the displayed icon type. + /// </summary> [Parameter] public PermaLinkStyle IconStyle { get; set; } = PermaLinkStyle.Normal; + /// <summary> + /// FLAG Enum value which sets the behaviour of the icon click: Copy, Navigate. + /// Flag values can be combined: PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate. + /// </summary> [Parameter] public PermaLinkIconActions IconActions { get; set; } = PermaLinkIconActions.Copy; + /// <summary> + /// Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. + /// </summary> [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> AdditionalAttributes { get; set; } + /// <summary> + /// Callback function called when Permalink icon clicked and PermaLinkIconActions.Copy feature was set. + /// </summary> [Parameter] public EventCallback<string> OnPermaLinkCopied { get; set; } private void MouseHover() From 0ecd3cc298caf6c623e860fbb40caee64edc365a Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Thu, 10 Jun 2021 19:32:12 +0200 Subject: [PATCH 26/69] Fix failed JS tests. --- .../PermaLinkElementTest.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs index 464e7511..8f339793 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs +++ b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs @@ -27,7 +27,12 @@ public void Init() _clipboardJsMock = new Mock<IClipboardHandler>(); _testContext.JSInterop.Mode = JSRuntimeMode.Strict; + +#if DEBUG _jsInteropModul = _testContext.JSInterop.SetupModule("./_content/Majorsoft.Blazor.Components.Common.JsInterop/elementInfo.js"); +#else + _jsInteropModul = _testContext.JSInterop.SetupModule("./_content/Majorsoft.Blazor.Components.Common.JsInterop/elementInfo.min.js"); +#endif _jsInteropModul.Setup<DomRect>("getBoundingClientRect", _ => true).SetResult(new DomRect()); _testContext.Services.Add(new ServiceDescriptor(typeof(ILogger<PermaLinkElement>), mock.Object)); From 608461b5593abc49112b13643d5fbb9b50111ae2 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Fri, 11 Jun 2021 13:23:42 +0200 Subject: [PATCH 27/69] Finalize PermalinkTests --- .../PermaLinkElementTest.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs index 8f339793..ee1b4f17 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs +++ b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkElementTest.cs @@ -8,6 +8,8 @@ using Moq; using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Web; namespace Majorsoft.Blazor.Components.PermaLink.Tests { @@ -169,7 +171,7 @@ public void PermaLinkElement_should_rendered_ShowPermaLinkIcon_Always_correctly( input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight""><a></a> <img style=""margin-top: 0px;"" width=""16"" height=""16"" class=""permaLinkIcon"" src=""_content/Majorsoft.Blazor.Components.PermaLink/link2.svg""> </div>"); } [TestMethod] - public void PermaLinkElement_should_rendered_ShowPermaLinkIcon_OnHover_correctly() + public async Task PermaLinkElement_should_rendered_ShowPermaLinkIcon_OnHover_correctly() { var rendered = _testContext.RenderComponent<PermaLinkElement>(parameters => parameters .Add(p => p.ShowIcon, ShowPermaLinkIcon.OnHover)); @@ -179,9 +181,17 @@ public void PermaLinkElement_should_rendered_ShowPermaLinkIcon_OnHover_correctly Assert.IsNotNull(input); input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight""><a></a> </div>"); - //input.MouseOver(); + await input.TriggerEventAsync("onmouseenter", new MouseEventArgs()); + rendered.WaitForAssertion(() => + { + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight""><a></a> <img style=""margin-top: 0px;"" width=""16"" height=""16"" class=""permaLinkIcon"" src=""_content/Majorsoft.Blazor.Components.PermaLink/link2.svg""> </div>"); + }); - //TODO: finish testing on mouse hover cases... + await input.TriggerEventAsync("onmouseleave", new MouseEventArgs()); + rendered.WaitForAssertion(() => + { + input.MarkupMatches(@$"<div id=""{input.Id}"" tabindex=""1000"" class=""permaDivRight""><a></a> </div>"); + }); } } } \ No newline at end of file From 232fbc83f38389b835ec076ab0eb7e79191d8533 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sun, 20 Jun 2021 20:59:42 +0200 Subject: [PATCH 28/69] Fixing page scroll elements. --- .../Majorsoft.Blazor.Components.Common.JsInterop.xml | 10 ++++++++++ .../Scroll/ScrollResult.cs | 10 ++++++++++ .../Scroll/ScrollToPageBottom.razor | 2 +- .../wwwroot/scroll.js | 8 ++++++-- .../wwwroot/scroll.min.js | 2 +- .../Majorsoft.Blazor.Components.Core.csproj | 1 + .../AnalyticsExtension.cs | 2 +- 7 files changed, 30 insertions(+), 5 deletions(-) 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 0a396b62..1b0ce47c 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 @@ -808,6 +808,16 @@ Scroll Y value </summary> </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollResult.IsPageTop"> + <summary> + Is scroll at page top + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollResult.IsPageBottom"> + <summary> + Is scroll at page bottom. + </summary> + </member> <member name="P:Majorsoft.Blazor.Components.Common.JsInterop.Scroll.ScrollToPageBottom.InnerElementReference"> <summary> Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollResult.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollResult.cs index 8461d601..96d4c408 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollResult.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollResult.cs @@ -13,5 +13,15 @@ public sealed class ScrollResult /// Scroll Y value /// </summary> public double Y { get; set; } + + /// <summary> + /// Is scroll at page top + /// </summary> + public bool IsPageTop { get; set; } + + /// <summary> + /// Is scroll at page bottom. + /// </summary> + public bool IsPageBottom { get; set; } } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor index df63813a..070a710e 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor @@ -36,7 +36,7 @@ var scrollSize = await _scrollHandler.GetPageScrollSizeAsync(); var percentage = (e.Y / scrollSize.Y) * 100; - var visible = percentage >= VisibleFromPagePercentage && percentage <= VisibleUntilPagePercentage; + var visible = percentage >= VisibleFromPagePercentage && percentage <= VisibleUntilPagePercentage && !scrollSize.IsPageBottom; if (visible != _isVisible) { _isVisible = visible; diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js index 98065be4..f412eff2 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/scroll.js @@ -145,7 +145,9 @@ export function getPageScrollPosition() { let args = { X: left, - Y: top + Y: top, + IsPageTop: window.scrollY == 0, + IsPageBottom: (window.innerHeight + window.scrollY) >= document.body.offsetHeight }; return args; @@ -153,7 +155,9 @@ export function getPageScrollPosition() { export function getPageScrollSize() { let args = { X: document.body.scrollWidth, - Y: document.body.scrollHeight + Y: document.body.scrollHeight, + IsPageTop: window.scrollY == 0, + IsPageBottom: (window.innerHeight + window.scrollY) >= document.body.offsetHeight }; return args; 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 9d0115a0..2e9795e3 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<n.offsetParent.scrollTop}export function scrollToEnd(t){t&&typeof t.scrollTop!==n&&typeof t.scrollHeight!==n&&(t.scrollTop=t.scrollHeight)}export function scrollToTop(t){t&&typeof t.scrollTop!==n&&(t.scrollTop=0)}export function scrollToX(t,i){t&&typeof t.scrollTop!==n&&(t.scrollTop=i)}export function scrollToY(t,i){t&&typeof t.scrollLeft!==n&&(t.scrollLeft=i)}export function getScrollXPosition(t){if(t&&typeof t.scrollTop!==n)return t.scrollTop}export function scrollToPageEnd(n){n?window.scrollTo({top:document.body.scrollHeight,behavior:"smooth"}):(document.body.scrollTop=document.body.scrollHeight,document.documentElement.scrollTop=document.body.scrollHeight)}export function scrollToPageTop(n){n?window.scrollTo({top:0,behavior:"smooth"}):(document.body.scrollTop=0,document.documentElement.scrollTop=0)}export function scrollToPageX(n,t){t?window.scrollTo({left:0,behavior:"smooth"}):(document.body.scrollLeft=n,document.documentElement.scrollLeft=n)}export function scrollToPageY(n,t){t?window.scrollTo({top:n,behavior:"smooth"}):(document.body.scrollTop=n,document.documentElement.scrollTop=n)}export function getPageScrollPosition(){let n=window.pageYOffset||document.documentElement.scrollTop,t=window.pageXOffset||document.documentElement.scrollLeft;return{X:t,Y:n}}export function getPageScrollSize(){return{X:document.body.scrollWidth,Y:document.body.scrollHeight}}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<n.length;i++)if(n[i].key===t){r=!0;break}r||n.push({key:t,handler:i})}function u(n,t){let i;for(let r=0;r<n.length;r++)if(n[r].key===t){i=n[r].handler;n.splice(r,1);break}return i}export function addScrollEvent(n,u){if(n&&u){let f=i(n);r(t,u,f);window.addEventListener("scroll",f,!1)}}export function removeScrollEvent(n){if(n){let i=u(t,n);i&&window.removeEventListener("scroll",i,!1)}}export function dispose(n){if(n)for(var t=0;t<n.length;t++)removeScrollEvent(n[t])}let n="undefined";let t=[]; \ No newline at end of file +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<n.offsetParent.scrollTop}export function scrollToEnd(t){t&&typeof t.scrollTop!==n&&typeof t.scrollHeight!==n&&(t.scrollTop=t.scrollHeight)}export function scrollToTop(t){t&&typeof t.scrollTop!==n&&(t.scrollTop=0)}export function scrollToX(t,i){t&&typeof t.scrollTop!==n&&(t.scrollTop=i)}export function scrollToY(t,i){t&&typeof t.scrollLeft!==n&&(t.scrollLeft=i)}export function getScrollXPosition(t){if(t&&typeof t.scrollTop!==n)return t.scrollTop}export function scrollToPageEnd(n){n?window.scrollTo({top:document.body.scrollHeight,behavior:"smooth"}):(document.body.scrollTop=document.body.scrollHeight,document.documentElement.scrollTop=document.body.scrollHeight)}export function scrollToPageTop(n){n?window.scrollTo({top:0,behavior:"smooth"}):(document.body.scrollTop=0,document.documentElement.scrollTop=0)}export function scrollToPageX(n,t){t?window.scrollTo({left:0,behavior:"smooth"}):(document.body.scrollLeft=n,document.documentElement.scrollLeft=n)}export function scrollToPageY(n,t){t?window.scrollTo({top:n,behavior:"smooth"}):(document.body.scrollTop=n,document.documentElement.scrollTop=n)}export function getPageScrollPosition(){let n=window.pageYOffset||document.documentElement.scrollTop,t=window.pageXOffset||document.documentElement.scrollLeft;return{X:t,Y:n,IsPageTop:window.scrollY==0,IsPageBottom:window.innerHeight+window.scrollY>=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<n.length;i++)if(n[i].key===t){r=!0;break}r||n.push({key:t,handler:i})}function u(n,t){let i;for(let r=0;r<n.length;r++)if(n[r].key===t){i=n[r].handler;n.splice(r,1);break}return i}export function addScrollEvent(n,u){if(n&&u){let f=i(n);r(t,u,f);window.addEventListener("scroll",f,!1)}}export function removeScrollEvent(n){if(n){let i=u(t,n);i&&window.removeEventListener("scroll",i,!1)}}export function dispose(n){if(n)for(var t=0;t<n.length;t++)removeScrollEvent(n[t])}let n="undefined";let t=[]; \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Core/Majorsoft.Blazor.Components.Core.csproj b/src/Majorsoft.Blazor.Components.Core/Majorsoft.Blazor.Components.Core.csproj index 2febc699..b3799f25 100644 --- a/src/Majorsoft.Blazor.Components.Core/Majorsoft.Blazor.Components.Core.csproj +++ b/src/Majorsoft.Blazor.Components.Core/Majorsoft.Blazor.Components.Core.csproj @@ -3,6 +3,7 @@ <PropertyGroup> <TargetFramework>net5.0</TargetFramework> <nullable>enable</nullable> + <PackageId>Majorsoft.Blazor.Components.Core</PackageId> </PropertyGroup> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs index 37f3fc92..2a3e54c7 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs @@ -15,7 +15,7 @@ public static class AnalyticsExtension /// <param name="services">IServiceCollection instance</param> /// <param name="trackingId">Google Tracking Id when provided <see cref="IGoogleAnalyticsService.Initialize(string)"/> will be called.</param> /// <returns>IServiceCollection</returns> - public static IServiceCollection AddGoogleAnalytics(this IServiceCollection services, string trackingId = null) + public static IServiceCollection AddGoogleAnalytics(this IServiceCollection services, string trackingId = "") { services.AddSingleton<IGoogleAnalyticsService, GoogleAnalyticsService>(); if (!string.IsNullOrWhiteSpace(trackingId)) From 8bcae6668c924133c0d28455fb5e856c32acf063 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sun, 20 Jun 2021 21:00:21 +0200 Subject: [PATCH 29/69] Issues with .NET 6 so disable pre-release version in global.json --- src/Majorsoft.Blazor.Components.TestApp/global.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/Majorsoft.Blazor.Components.TestApp/global.json diff --git a/src/Majorsoft.Blazor.Components.TestApp/global.json b/src/Majorsoft.Blazor.Components.TestApp/global.json new file mode 100644 index 00000000..a8ab760d --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApp/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "allowPrerelease": false + } +} \ No newline at end of file From 4c2e16a081a1cdfbb7de62c21facfc7558ce4967 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 21 Jun 2021 16:21:05 +0200 Subject: [PATCH 30/69] Fix Blazor server issues with Scroll components. --- .../Scroll/ScrollToPageBottom.razor | 189 +++++++++--------- .../Scroll/ScrollToPageTop.razor | 31 +-- 2 files changed, 113 insertions(+), 107 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor index 070a710e..46de05a1 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageBottom.razor @@ -1,22 +1,22 @@ <div @ref="_inputRef" class="scrollToPageBottom @HorizontalPosition.ToString().ToLower() @(_isVisible ? "" : "hidden")" tabindex="500" - @onclick="async () => await _scrollHandler.ScrollToPageEndAsync(SmootScroll)"> - @Content + @onclick="async () => await _scrollHandler.ScrollToPageEndAsync(SmootScroll)"> + @Content </div> <style> - .scrollToPageBottom { - top: @(PaddingFromTop)px; - opacity: @(_isVisible ? (AnimateOnHover ? 0.7 : 1) : 0); - } - .scrollToPageBottom:hover { - opacity: @(_isVisible ? 1 : 0); - } - .scrollToPageBottom.right { - right: @(PaddingFromSide)px; - } - .scrollToPageBottom.left { - left: @(PaddingFromSide)px; - } + .scrollToPageBottom { + top: @(PaddingFromTop)px; + opacity: @(_isVisible ? (AnimateOnHover ? 0.7 : 1) : 0); + } + .scrollToPageBottom:hover { + opacity: @(_isVisible ? 1 : 0); + } + .scrollToPageBottom.right { + right: @(PaddingFromSide)px; + } + .scrollToPageBottom.left { + left: @(PaddingFromSide)px; + } </style> @implements IAsyncDisposable @@ -25,82 +25,85 @@ @inject ILogger<ScrollToPageBottom> _logger @code { - private bool _isVisible = false; - - protected override async Task OnInitializedAsync() - { - WriteDiag("Component initialized..."); - - await _scrollHandler.RegisterPageScrollAsync(async (e) => - { - var scrollSize = await _scrollHandler.GetPageScrollSizeAsync(); - var percentage = (e.Y / scrollSize.Y) * 100; - - var visible = percentage >= VisibleFromPagePercentage && percentage <= VisibleUntilPagePercentage && !scrollSize.IsPageBottom; - if (visible != _isVisible) - { - _isVisible = visible; - StateHasChanged(); - - WriteDiag($"Component visibilit changed visible: {_isVisible}."); - } - }); - } - - private ElementReference _inputRef; - /// <summary> - /// Exposes a Blazor <see cref="ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. - /// </summary> - public ElementReference InnerElementReference => _inputRef; - - /// <summary> - /// HTML Content of the scroll panel. - /// </summary> - [Parameter] public RenderFragment Content { get; set; } - - /// <summary> - /// Element should be visible when scroll reached page % of given value. - /// </summary> - [Parameter] public byte VisibleFromPagePercentage { get; set; } = 5; - /// <summary> - /// Element should be visible until scroll reached page % of given value. - /// </summary> - [Parameter] public byte VisibleUntilPagePercentage { get; set; } = 80; - - /// <summary> - /// Scroll should be jump or smoothly scroll. - /// </summary> - [Parameter] public bool SmootScroll { get; set; } = true; - - /// <summary> - /// Apply animation (opacity) on icon when mouse hovered. - /// </summary> - [Parameter] public bool AnimateOnHover { get; set; } = true; - - /// <summary> - /// Required space from page bottom in px. - /// </summary> - [Parameter] public int PaddingFromTop { get; set; } = 24; - /// <summary> - /// Required space from page (left/right) side in px. - /// </summary> - [Parameter] public int PaddingFromSide { get; set; } = 24; - /// <summary> - /// Element position on page {Right, Left}. - /// </summary> - [Parameter] public PageScrollHorizontalPosition HorizontalPosition { get; set; } = PageScrollHorizontalPosition.Right; - - - private void WriteDiag(string message) - { - _logger.LogDebug($"Component {this.GetType()}: {message}"); - } - - public async ValueTask DisposeAsync() - { - if (_scrollHandler is not null) - { - await _scrollHandler.DisposeAsync(); - } - } + private bool _isVisible = false; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if(firstRender) + { + WriteDiag("Component rendered..."); + + await _scrollHandler.RegisterPageScrollAsync(async (e) => + { + var scrollSize = await _scrollHandler.GetPageScrollSizeAsync(); + var percentage = (e.Y / scrollSize.Y) * 100; + + var visible = percentage >= VisibleFromPagePercentage && percentage <= VisibleUntilPagePercentage && !scrollSize.IsPageBottom; + if (visible != _isVisible) + { + _isVisible = visible; + StateHasChanged(); + + WriteDiag($"Component visibilit changed visible: {_isVisible}."); + } + }); + } + } + + private ElementReference _inputRef; + /// <summary> + /// Exposes a Blazor <see cref="ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + /// </summary> + public ElementReference InnerElementReference => _inputRef; + + /// <summary> + /// HTML Content of the scroll panel. + /// </summary> + [Parameter] public RenderFragment Content { get; set; } + + /// <summary> + /// Element should be visible when scroll reached page % of given value. + /// </summary> + [Parameter] public byte VisibleFromPagePercentage { get; set; } = 5; + /// <summary> + /// Element should be visible until scroll reached page % of given value. + /// </summary> + [Parameter] public byte VisibleUntilPagePercentage { get; set; } = 80; + + /// <summary> + /// Scroll should be jump or smoothly scroll. + /// </summary> + [Parameter] public bool SmootScroll { get; set; } = true; + + /// <summary> + /// Apply animation (opacity) on icon when mouse hovered. + /// </summary> + [Parameter] public bool AnimateOnHover { get; set; } = true; + + /// <summary> + /// Required space from page bottom in px. + /// </summary> + [Parameter] public int PaddingFromTop { get; set; } = 24; + /// <summary> + /// Required space from page (left/right) side in px. + /// </summary> + [Parameter] public int PaddingFromSide { get; set; } = 24; + /// <summary> + /// Element position on page {Right, Left}. + /// </summary> + [Parameter] public PageScrollHorizontalPosition HorizontalPosition { get; set; } = PageScrollHorizontalPosition.Right; + + + private void WriteDiag(string message) + { + _logger.LogDebug($"Component {this.GetType()}: {message}"); + } + + public async ValueTask DisposeAsync() + { + if (_scrollHandler is not null) + { + await _scrollHandler.DisposeAsync(); + } + } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor index 0e65bac3..e9d9e97a 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/Scroll/ScrollToPageTop.razor @@ -27,24 +27,27 @@ @code { private bool _isVisible = false; - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { - WriteDiag("Component initialized..."); - - await _scrollHandler.RegisterPageScrollAsync(async (e) => + if (firstRender) { - var scrollSize = await _scrollHandler.GetPageScrollSizeAsync(); - var percentage = (e.Y / scrollSize.Y) * 100; + WriteDiag("Component rendered..."); - var visible = percentage >= VisibleFromPagePercentage; - if (visible != _isVisible) + await _scrollHandler.RegisterPageScrollAsync(async (e) => { - _isVisible = visible; - StateHasChanged(); - - WriteDiag($"Component visibilit changed visible: {_isVisible}."); - } - }); + var scrollSize = await _scrollHandler.GetPageScrollSizeAsync(); + var percentage = (e.Y / scrollSize.Y) * 100; + + var visible = percentage >= VisibleFromPagePercentage; + if (visible != _isVisible) + { + _isVisible = visible; + StateHasChanged(); + + WriteDiag($"Component visibilit changed visible: {_isVisible}."); + } + }); + } } private ElementReference _inputRef; From a172378dbeadc0e81501cb7e4a5de2671cc612c8 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 21 Jun 2021 19:06:45 +0200 Subject: [PATCH 31/69] Change Google analytics Get() to accept ExpandoObject. --- .../Components/GoogleAnalytics.razor | 67 ++++++++++++------- .../_Imports.razor | 4 +- .../Startup.cs | 3 + .../AnalyticsExtension.cs | 4 +- .../Google/GoogleAnalyticsService.cs | 9 +-- .../Majorsoft.Blazor.Extensions.Analytics.xml | 6 +- .../wwwroot/googleAnalytics.js | 19 ++++-- .../wwwroot/googleAnalytics.min.js | 4 +- 8 files changed, 74 insertions(+), 42 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor index 8148812d..12523988 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor @@ -1,34 +1,49 @@ <PageScroll /> <div class="container-fluid p-3 mb-3 border rounded"> - <h1>Website Analytics extensions</h1> - <p> - Blazor extension that enables WebAssemply site analytics e.g. Google, etc. For usage see source code and docs on - <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Analytics.md" target="_blank">Github</a>. - <br /><strong>Majorsoft.Blazor.Extensions.Analytics</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Extensions.Analytics" target="_blank">Nuget</a> - </p> - - <div> - <h3>Supported Analytics service providers:</h3> - <ul> - <li><NavLink href="analytics#google">Google Analytics</NavLink></li> - </ul> - </div> - - <div class="container-fluid p-3 mb-3 border rounded"> - <PermaLinkElement PermaLinkName="google" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> - <Content><h3>Google Analytics</h3></Content> - </PermaLinkElement> - <p> - <strong>Google Analytics</strong> is a web analytics service offered by Google that tracks and reports website traffic, etc. inside the Google Marketing Platform. - <br /> - <strong><code>IGoogleAnalyticsService</code> is an injectable service</strong> for enabling - <a href="https://support.google.com/analytics/answer/1008015?hl=en#" target="_blank">Google Analytics</a> page tracking in Blazor Apps. - </p> - </div> + <h1>Website Analytics extensions</h1> + <p> + Blazor extension that enables WebAssemply site analytics e.g. Google, etc. For usage see source code and docs on + <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Analytics.md" target="_blank">Github</a>. + <br /><strong>Majorsoft.Blazor.Extensions.Analytics</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Extensions.Analytics" target="_blank">Nuget</a> + </p> + + <div> + <h3>Supported Analytics service providers:</h3> + <ul> + <li><NavLink href="analytics#google">Google Analytics</NavLink></li> + </ul> + </div> + + <div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="google" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>Google Analytics</h3></Content> + </PermaLinkElement> + <p> + <strong>Google Analytics</strong> is a web analytics service offered by Google that tracks and reports website traffic, etc. inside the Google Marketing Platform. + <br /> + <strong><code>IGoogleAnalyticsService</code> is an injectable service</strong> for enabling + <a href="https://support.google.com/analytics/answer/1008015?hl=en#" target="_blank">Google Analytics</a> page tracking in Blazor Apps. + </p> + </div> </div> -@code { +@using System.Dynamic; +@inject IGoogleAnalyticsService _googleAnalytincsService; + +@code { + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if(firstRender) + { + await _googleAnalytincsService.Initialize("G-1QD2VGTEWX"); + + dynamic exp = new ExpandoObject(); + exp.test = 27; + await _googleAnalytincsService.Set(exp); + await _googleAnalytincsService.Get("test"); + } + } } \ 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 d7a6adc7..5702ed14 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor @@ -46,4 +46,6 @@ @using Majorsoft.Blazor.Components.Maps @using Majorsoft.Blazor.Components.Maps.Google -@using Majorsoft.Blazor.Extensions.BrowserStorage \ No newline at end of file +@using Majorsoft.Blazor.Extensions.BrowserStorage + +@using Majorsoft.Blazor.Extensions.Analytics.Google \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs b/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs index 892505d8..4456c637 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs @@ -11,6 +11,7 @@ using Majorsoft.Blazor.Server.Logging.Console; using Majorsoft.Blazor.Components.Maps; using Majorsoft.Blazor.Extensions.BrowserStorage; +using Majorsoft.Blazor.Extensions.Analytics; namespace Majorsoft.Blazor.Components.TestServerApp { @@ -35,6 +36,8 @@ public void ConfigureServices(IServiceCollection services) services.AddMapExtensions(); services.AddBrowserStorage(); + + services.AddGoogleAnalytics(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs index 2a3e54c7..f3eaf7a1 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs @@ -13,11 +13,11 @@ public static class AnalyticsExtension /// Registers required Google Analytic services into IServiceCollection /// </summary> /// <param name="services">IServiceCollection instance</param> - /// <param name="trackingId">Google Tracking Id when provided <see cref="IGoogleAnalyticsService.Initialize(string)"/> will be called.</param> + /// <param name="trackingId">Google Tracking Id when provided <see cref="IGoogleAnalyticsService.Initialize(string)"/> will be called. DO NOT CALL with value in case of Blazor Server!</param> /// <returns>IServiceCollection</returns> public static IServiceCollection AddGoogleAnalytics(this IServiceCollection services, string trackingId = "") { - services.AddSingleton<IGoogleAnalyticsService, GoogleAnalyticsService>(); + services.AddTransient<IGoogleAnalyticsService, GoogleAnalyticsService>(); if (!string.IsNullOrWhiteSpace(trackingId)) { services.BuildServiceProvider().GetRequiredService<IGoogleAnalyticsService>()?.Initialize(trackingId); diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs index 447dca18..81206d26 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Dynamic; using System.Linq; using System.Threading.Tasks; @@ -44,9 +45,9 @@ public interface IGoogleAnalyticsService : IAsyncDisposable /// <summary> /// Allows you to set values that persist across all the subsequent gtag() calls on the page. /// </summary> - /// <param name="parameterValuePair">Is a key name and the value that is to persist across gtag() calls.</param> + /// <param name="parameters">Is a key name and the value that is to persist across gtag() calls.</param> /// <returns>Async ValueTask</returns> - ValueTask Set(Dictionary<string, object>? parameterValuePair = null); + ValueTask Set(ExpandoObject parameters); /// <summary> /// Use the event command to send event data. @@ -101,10 +102,10 @@ public async ValueTask Get(string fieldName, string trackingId = "") var module = await moduleTask.Value; await module.InvokeVoidAsync("get", string.IsNullOrWhiteSpace(trackingId) ? TrackingId : trackingId, fieldName); //TODO: callback results } - public async ValueTask Set(Dictionary<string, object>? parameterValuePair = null) + public async ValueTask Set(ExpandoObject parameters) { var module = await moduleTask.Value; - await module.InvokeVoidAsync("set", parameterValuePair?.ToList()); + await module.InvokeVoidAsync("set", parameters); } public async ValueTask Event(string eventName, Dictionary<string, object> eventParams) { diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml index a699fc81..bbd86d39 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml @@ -14,7 +14,7 @@ Registers required Google Analytic services into IServiceCollection </summary> <param name="services">IServiceCollection instance</param> - <param name="trackingId">Google Tracking Id when provided <see cref="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Initialize(System.String)"/> will be called.</param> + <param name="trackingId">Google Tracking Id when provided <see cref="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Initialize(System.String)"/> will be called. DO NOT CALL with value in case of Blazor Server!</param> <returns>IServiceCollection</returns> </member> <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService"> @@ -51,11 +51,11 @@ <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> <returns>Async ValueTask</returns> </member> - <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Set(System.Collections.Generic.Dictionary{System.String,System.Object})"> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Set(System.Dynamic.ExpandoObject)"> <summary> Allows you to set values that persist across all the subsequent gtag() calls on the page. </summary> - <param name="parameterValuePair">Is a key name and the value that is to persist across gtag() calls.</param> + <param name="parameters">Is a key name and the value that is to persist across gtag() calls.</param> <returns>Async ValueTask</returns> </member> <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Event(System.String,System.Collections.Generic.Dictionary{System.String,System.Object})"> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js index 22c893d1..a5ddd969 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js @@ -4,13 +4,23 @@ export function init(trackingId) { } let src = "https://www.googletagmanager.com/gtag/js?id="; + let scriptsIncluded = false; + let scriptTags = document.querySelectorAll('head > script'); scriptTags.forEach(scriptTag => { - if (scriptTag.getAttribute('src').startsWith(src)) { - return; + if (scriptTag) { + let srcAttribute = scriptTag.getAttribute('src'); + if (srcAttribute && srcAttribute.startsWith(src)) { + scriptsIncluded = true; + return; + } } }); + if (scriptsIncluded && window.gtag) { //Prevent adding JS scripts to page multiple times. + return; + } + //Inject required Google JS scripts to HTML (only once!) document.head.appendChild(document.createComment("Global site tag (gtag.js) - Google Analytics")); @@ -38,13 +48,14 @@ export function config(trackingId, data) { export function get(trackingId, fieldName) { if (trackingId && window.gtag) { gtag('get', trackingId, fieldName, (result) => { - //callback method here... + //TOOD: callback method here... + console.log(result); }); } } export function set(data) { if (window.gtag && data) { - gtag('set', trackingId, data); + gtag('set', data); } } export function event(eventName, data) { diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js index 5594c0f8..0a1a89f5 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js @@ -1,5 +1,5 @@ -export function init(n){if(n){let i="https://www.googletagmanager.com/gtag/js?id=",u=document.querySelectorAll("head > script");u.forEach(n=>{n.getAttribute("src").startsWith(i)});document.head.appendChild(document.createComment("Global site tag (gtag.js) - Google Analytics"));let t=document.createElement("script");t.src=i+n;t.async=!0;document.head.appendChild(t);let r=document.createElement("script");r.textContent=`window.dataLayer = window.dataLayer || []; +export function init(n){if(n){let i="https://www.googletagmanager.com/gtag/js?id=",r=!1,f=document.querySelectorAll("head > script");if(f.forEach(n=>{if(n){let t=n.getAttribute("src");if(t&&t.startsWith(i)){r=!0;return}}}),!r||!window.gtag){document.head.appendChild(document.createComment("Global site tag (gtag.js) - Google Analytics"));let t=document.createElement("script");t.src=i+n;t.async=!0;document.head.appendChild(t);let u=document.createElement("script");u.textContent=`window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); - gtag('config', '{0}');`.replace("{0}",n);document.head.appendChild(r)}}export function config(n,t){n&&window.gtag&>ag("config",n,t)}export function get(n,t){n&&window.gtag&>ag("get",n,t,()=>{})}export function set(n){window.gtag&&n&>ag("set",trackingId,n)}export function event(n,t){window.gtag&&n&&t&>ag("event",n,t)} \ No newline at end of file + gtag('config', '{0}');`.replace("{0}",n);document.head.appendChild(u)}}}export function config(n,t){n&&window.gtag&>ag("config",n,t)}export function get(n,t){n&&window.gtag&>ag("get",n,t,n=>{console.log(n)})}export function set(n){window.gtag&&n&>ag("set",n)}export function event(n,t){window.gtag&&n&&t&>ag("event",n,t)} \ No newline at end of file From 17adac3cf9812f4d0331903acf7807ac72554aa3 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 21 Jun 2021 19:07:00 +0200 Subject: [PATCH 32/69] Fix GoogleMaps JS script insert. --- .../wwwroot/googleMaps.js | 13 +++++++++---- .../wwwroot/googleMaps.min.js | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js index bdb034c9..76db8114 100644 --- a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js +++ b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.js @@ -5,12 +5,17 @@ storeElementIdWithDotnetRef(_mapsElementDict, elementId, dotnetRef, backgroundColor, controlSize); //Store map info + let src = "https://maps.googleapis.com/maps/api/js?key="; let scriptsIncluded = false; + let scriptTags = document.querySelectorAll('head > script'); scriptTags.forEach(scriptTag => { - if (scriptTag.getAttribute('src').startsWith("https://maps.googleapis.com/maps/api/js?key=")) { - scriptsIncluded = true; - return; + if (scriptTag) { + let srcAttribute = scriptTag.getAttribute('src'); + if (srcAttribute && srcAttribute.startsWith(src)) { + scriptsIncluded = true; + return; + } } }); @@ -26,7 +31,7 @@ importedPoly.src = "https://polyfill.io/v3/polyfill.min.js?features=default"; document.head.appendChild(importedPoly); - let src = "https://maps.googleapis.com/maps/api/js?key=" + key + "&callback=initGoogleMaps&libraries=&v=weekly"; + src = src + key + "&callback=initGoogleMaps&libraries=&v=weekly"; let importedMaps = document.createElement('script'); importedMaps.src = src; importedMaps.defer = true; diff --git a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js index 26b1cf06..68bbd396 100644 --- a/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js +++ b/src/Majorsoft.Blazor.Components.Maps/wwwroot/googleMaps.min.js @@ -1 +1 @@ -export function init(t,i,r,f,e){if(t&&i&&r){u(n,i,r,f,e);let s=!1,c=document.querySelectorAll("head > script");if(c.forEach(n=>{if(n.getAttribute("src").startsWith("https://maps.googleapis.com/maps/api/js?key=")){s=!0;return}}),s){window.google&&window.initGoogleMaps();return}let h=document.createElement("script");h.src="https://polyfill.io/v3/polyfill.min.js?features=default";document.head.appendChild(h);let l="https://maps.googleapis.com/maps/api/js?key="+t+"&callback=initGoogleMaps&libraries=&v=weekly",o=document.createElement("script");o.src=l;o.defer=!0;document.head.appendChild(o)}}function u(n,t,i,r,u){for(let i=0;i<n.length;i++)if(n[i].key===t)return;!1||n.push({key:t,value:{ref:i,map:null,bgColor:r,ctrSize:u}})}function f(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t){n.splice(i,1);break}}function t(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t)return n[i].value}export function setCenterCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.setCenter({lat:r,lng:u})}}export function setCenterAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.setCenter(n[0].geometry.location)})}}export function panToCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.panTo({lat:r,lng:u})}}export function panToAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.panTo(n[0].geometry.location)})}}export function setZoom(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setZoom(r)}}export function setMapType(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setMapTypeId(r)}}export function setHeading(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setHeading(r)}}export function setTilt(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setTilt(r)}}export function setClickableIcons(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setClickableIcons(r)}}export function setOptions(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setOptions(r)}}export function resizeMap(i){if(i){let r=t(n,i);r&&r.map&&google.maps.event.trigger(r.map,"resize")}}export function createCustomControls(i,r){if(i&&r){let f=t(n,i);if(f&&f.map)for(var u=0;u<r.length;u++){let n=r[u],t=document.createElement("div");t.innerHTML=n.content;f.map.controls[n.controlPosition].push(t);let i=n.id,e=f.ref;t.addEventListener("click",()=>{e.invokeMethodAsync("CustomControlClicked",i)})}}}export function createMarkers(r,u){if(r&&u&&u.length){let o=t(n,r);if(o&&o.map)for(var f=0;f<u.length;f++){let n=u[f],t=new google.maps.Marker({id:n.id,crossOnDrag:n.crossOnDrag,optimized:n.optimized});if(t.setMap(o.map),e(n,t),i.push(t),n.clickable){let i=null;n.infoWindow&&(i=new google.maps.InfoWindow({content:n.infoWindow.content,maxWidth:n.infoWindow.maxWidth}));t.addListener("click",()=>{o.ref.invokeMethodAsync("MarkerClicked",n.id),i&&i.open(o.map,t)})}if(n.draggable){t.addListener("drag",()=>{i("MarkerDrag",n.id,t.getPosition().toJSON())});t.addListener("dragend",()=>{i("MarkerDragEnd",n.id,t.getPosition().toJSON())});t.addListener("dragstart",()=>{i("MarkerDragStart",n.id,t.getPosition().toJSON())});function i(n,t,i){let r={Latitude:i.lat,Longitude:i.lng};o.ref.invokeMethodAsync(n,t,r)}}}}}export function removeMarkers(r,u){if(r&&u&&u.length){let e=t(n,r);if(e&&e.map)for(var f=0;f<u.length;f++){let n=u[f];i.forEach((t,r)=>{if(n.id==t.id){t.setMap(null);i.splice(r,1);return}})}}}function e(n,t){t&&n&&(t.setPosition({lat:n.position.latitude,lng:n.position.longitude}),t.anchorPoint=n.anchorPoint?{x:n.anchorPoint.x,y:n.anchorPoint.y}:null,t.setAnimation(n.animation),t.setClickable(n.clickable),t.crossOnDrag=n.crossOnDrag,t.setCursor(n.cursor),t.setDraggable(n.draggable),t.setIcon(n.icon),t.setLabel(n.label),t.setOpacity(n.opacity),t.optimized=n.optimized,t.setShape(n.shape),t.setTitle(n.title),t.setVisible(n.visible),t.setZIndex(n.zIndex))}export function getAddressCoordinates(i,u){r(u,function(r){if(r){let u=t(n,i);u&&u.map&&u.ref.invokeMethodAsync("AddressSearch",r)}})}function r(n,t){let i=new google.maps.Geocoder;i.geocode({address:n},function(n,i){i==google.maps.GeocoderStatus.OK&&t(n)})}export function dispose(i){if(i){let r=t(n,i);r.map=null;r.ref=null;f(n,i)}}window.initGoogleMaps=()=>{for(let i=0;i<n.length;i++){let f=n[i].key,e=n[i].value;if(!n[i].value.map){let r=new google.maps.Map(document.getElementById(f),{backgroundColor:e.bgColor,controlSize:e.ctrSize});r.elementId=f;n[i].value.map=r;function u(i,u){if(r&&r.elementId&&i){let f=t(n,r.elementId);if(f){let n=i.latLng.toJSON(),t={Latitude:n.lat,Longitude:n.lng};f.ref.invokeMethodAsync(u,t)}}}r.addListener("click",n=>{u(n,"MapClicked")});r.addListener("dblclick",n=>{u(n,"MapDoubleClicked")});r.addListener("contextmenu",n=>{u(n,"MapContextMenu")});r.addListener("mouseup",n=>{u(n,"MapMouseUp")});r.addListener("mousedown",n=>{u(n,"MapMouseDown")});r.addListener("mousemove",n=>{u(n,"MapMouseMove")});r.addListener("mouseover",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOver")}});r.addListener("mouseout",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOut")}});r.addListener("center_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapCenterChanged",t)}}});r.addListener("zoom_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapZoomChanged",r.getZoom())}});r.addListener("maptypeid_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTypeIdChanged",r.getMapTypeId())}});r.addListener("heading_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapHeadingChanged",r.getHeading())}});r.addListener("tilt_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTiltChanged",r.getTilt())}});r.addListener("bounds_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapBoundsChanged")}});r.addListener("projection_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapProjectionChanged")}});r.addListener("draggable_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapDraggableChanged")}});r.addListener("streetview_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapStreetviewChanged")}});r.addListener("drag",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDrag",t)}}});r.addListener("dragend",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragEnd",t)}}});r.addListener("dragstart",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragStart",t)}}});r.addListener("resize",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i){let n={Width:r.getDiv().offsetWidth,Height:r.getDiv().offsetHeight};i.ref.invokeMethodAsync("MapResized",n)}}});r.addListener("tilesloaded",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTilesLoaded")}});r.addListener("idle",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapIdle")}});n[i].value.ref.invokeMethodAsync("MapInitialized",f)}}};let n=[],i=[]; \ No newline at end of file +export function init(t,i,r,f,e){if(t&&i&&r){u(n,i,r,f,e);let o="https://maps.googleapis.com/maps/api/js?key=",h=!1,l=document.querySelectorAll("head > script");if(l.forEach(n=>{if(n){let t=n.getAttribute("src");if(t&&t.startsWith(o)){h=!0;return}}}),h){window.google&&window.initGoogleMaps();return}let c=document.createElement("script");c.src="https://polyfill.io/v3/polyfill.min.js?features=default";document.head.appendChild(c);o=o+t+"&callback=initGoogleMaps&libraries=&v=weekly";let s=document.createElement("script");s.src=o;s.defer=!0;document.head.appendChild(s)}}function u(n,t,i,r,u){for(let i=0;i<n.length;i++)if(n[i].key===t)return;!1||n.push({key:t,value:{ref:i,map:null,bgColor:r,ctrSize:u}})}function f(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t){n.splice(i,1);break}}function t(n,t){for(let i=0;i<n.length;i++)if(n[i].key===t)return n[i].value}export function setCenterCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.setCenter({lat:r,lng:u})}}export function setCenterAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.setCenter(n[0].geometry.location)})}}export function panToCoords(i,r,u){if(i){let f=t(n,i);f&&f.map&&f.map.panTo({lat:r,lng:u})}}export function panToAddress(i,u){if(i){let f=t(n,i);f&&f.map&&r(u,function(n){n&&f.map.panTo(n[0].geometry.location)})}}export function setZoom(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setZoom(r)}}export function setMapType(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setMapTypeId(r)}}export function setHeading(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setHeading(r)}}export function setTilt(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setTilt(r)}}export function setClickableIcons(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setClickableIcons(r)}}export function setOptions(i,r){if(i){let u=t(n,i);u&&u.map&&u.map.setOptions(r)}}export function resizeMap(i){if(i){let r=t(n,i);r&&r.map&&google.maps.event.trigger(r.map,"resize")}}export function createCustomControls(i,r){if(i&&r){let f=t(n,i);if(f&&f.map)for(var u=0;u<r.length;u++){let n=r[u],t=document.createElement("div");t.innerHTML=n.content;f.map.controls[n.controlPosition].push(t);let i=n.id,e=f.ref;t.addEventListener("click",()=>{e.invokeMethodAsync("CustomControlClicked",i)})}}}export function createMarkers(r,u){if(r&&u&&u.length){let o=t(n,r);if(o&&o.map)for(var f=0;f<u.length;f++){let n=u[f],t=new google.maps.Marker({id:n.id,crossOnDrag:n.crossOnDrag,optimized:n.optimized});if(t.setMap(o.map),e(n,t),i.push(t),n.clickable){let i=null;n.infoWindow&&(i=new google.maps.InfoWindow({content:n.infoWindow.content,maxWidth:n.infoWindow.maxWidth}));t.addListener("click",()=>{o.ref.invokeMethodAsync("MarkerClicked",n.id),i&&i.open(o.map,t)})}if(n.draggable){t.addListener("drag",()=>{i("MarkerDrag",n.id,t.getPosition().toJSON())});t.addListener("dragend",()=>{i("MarkerDragEnd",n.id,t.getPosition().toJSON())});t.addListener("dragstart",()=>{i("MarkerDragStart",n.id,t.getPosition().toJSON())});function i(n,t,i){let r={Latitude:i.lat,Longitude:i.lng};o.ref.invokeMethodAsync(n,t,r)}}}}}export function removeMarkers(r,u){if(r&&u&&u.length){let e=t(n,r);if(e&&e.map)for(var f=0;f<u.length;f++){let n=u[f];i.forEach((t,r)=>{if(n.id==t.id){t.setMap(null);i.splice(r,1);return}})}}}function e(n,t){t&&n&&(t.setPosition({lat:n.position.latitude,lng:n.position.longitude}),t.anchorPoint=n.anchorPoint?{x:n.anchorPoint.x,y:n.anchorPoint.y}:null,t.setAnimation(n.animation),t.setClickable(n.clickable),t.crossOnDrag=n.crossOnDrag,t.setCursor(n.cursor),t.setDraggable(n.draggable),t.setIcon(n.icon),t.setLabel(n.label),t.setOpacity(n.opacity),t.optimized=n.optimized,t.setShape(n.shape),t.setTitle(n.title),t.setVisible(n.visible),t.setZIndex(n.zIndex))}export function getAddressCoordinates(i,u){r(u,function(r){if(r){let u=t(n,i);u&&u.map&&u.ref.invokeMethodAsync("AddressSearch",r)}})}function r(n,t){let i=new google.maps.Geocoder;i.geocode({address:n},function(n,i){i==google.maps.GeocoderStatus.OK&&t(n)})}export function dispose(i){if(i){let r=t(n,i);r.map=null;r.ref=null;f(n,i)}}window.initGoogleMaps=()=>{for(let i=0;i<n.length;i++){let f=n[i].key,e=n[i].value;if(!n[i].value.map){let r=new google.maps.Map(document.getElementById(f),{backgroundColor:e.bgColor,controlSize:e.ctrSize});r.elementId=f;n[i].value.map=r;function u(i,u){if(r&&r.elementId&&i){let f=t(n,r.elementId);if(f){let n=i.latLng.toJSON(),t={Latitude:n.lat,Longitude:n.lng};f.ref.invokeMethodAsync(u,t)}}}r.addListener("click",n=>{u(n,"MapClicked")});r.addListener("dblclick",n=>{u(n,"MapDoubleClicked")});r.addListener("contextmenu",n=>{u(n,"MapContextMenu")});r.addListener("mouseup",n=>{u(n,"MapMouseUp")});r.addListener("mousedown",n=>{u(n,"MapMouseDown")});r.addListener("mousemove",n=>{u(n,"MapMouseMove")});r.addListener("mouseover",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOver")}});r.addListener("mouseout",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapMouseOut")}});r.addListener("center_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapCenterChanged",t)}}});r.addListener("zoom_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapZoomChanged",r.getZoom())}});r.addListener("maptypeid_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTypeIdChanged",r.getMapTypeId())}});r.addListener("heading_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapHeadingChanged",r.getHeading())}});r.addListener("tilt_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTiltChanged",r.getTilt())}});r.addListener("bounds_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapBoundsChanged")}});r.addListener("projection_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapProjectionChanged")}});r.addListener("draggable_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapDraggableChanged")}});r.addListener("streetview_changed",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapStreetviewChanged")}});r.addListener("drag",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDrag",t)}}});r.addListener("dragend",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragEnd",t)}}});r.addListener("dragstart",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i&&r.getCenter()){let n=r.getCenter().toJSON(),t={Latitude:n.lat,Longitude:n.lng};i.ref.invokeMethodAsync("MapDragStart",t)}}});r.addListener("resize",()=>{if(r&&r.elementId){let i=t(n,r.elementId);if(i){let n={Width:r.getDiv().offsetWidth,Height:r.getDiv().offsetHeight};i.ref.invokeMethodAsync("MapResized",n)}}});r.addListener("tilesloaded",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapTilesLoaded")}});r.addListener("idle",()=>{if(r&&r.elementId){let i=t(n,r.elementId);i&&i.ref.invokeMethodAsync("MapIdle")}});n[i].value.ref.invokeMethodAsync("MapInitialized",f)}}};let n=[],i=[]; \ No newline at end of file From a2759dbaf440032d2592358fdcc2b91cfab4a2a0 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 21 Jun 2021 21:33:30 +0200 Subject: [PATCH 33/69] Implement Google analytics get/set/event --- .../Components/GoogleAnalytics.razor | 34 ++++++++-- .../Shared/MainLayout.razor | 15 ++++- .../Startup.cs | 2 +- .../_Imports.razor | 4 +- .../AnalyticsExtension.cs | 15 ++++- .../Google/GoogleAnalyticsCustomEventArgs.cs | 29 +++++++++ .../Google/GoogleAnalyticsEventTypes.cs | 33 ++++++++++ .../Google/GoogleAnalyticsService.cs | 45 ++++++++----- .../Majorsoft.Blazor.Extensions.Analytics.xml | 64 ++++++++++++++++--- .../wwwroot/googleAnalytics.js | 10 +++ .../wwwroot/googleAnalytics.min.js | 2 +- 11 files changed, 218 insertions(+), 35 deletions(-) create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsCustomEventArgs.cs create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsEventTypes.cs diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor index 12523988..cd4eec9f 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor @@ -29,21 +29,45 @@ </div> -@using System.Dynamic; -@inject IGoogleAnalyticsService _googleAnalytincsService; +@using System.Dynamic +@inject IGoogleAnalyticsService _googleAnalytincsService +@implements IAsyncDisposable @code { protected override async Task OnAfterRenderAsync(bool firstRender) { if(firstRender) { - await _googleAnalytincsService.Initialize("G-1QD2VGTEWX"); + /*var sessionId = */ + await _googleAnalytincsService.GetAsync("session_id"); + //custom set/get dynamic exp = new ExpandoObject(); exp.test = 27; - await _googleAnalytincsService.Set(exp); - await _googleAnalytincsService.Get("test"); + await _googleAnalytincsService.SetAsync(exp); + /*var test =*/ + await _googleAnalytincsService.GetAsync("test"); + + dynamic exp2 = new ExpandoObject(); + exp2.search_term = "heeeeeeeeeeee"; + await _googleAnalytincsService.EventAsync(GoogleAnalyticsEventTypes.search, exp2); + + await _googleAnalytincsService.CustomEventAsync("testEvent", new GoogleAnalyticsCustomEventArgs() + { + Action = "Test action", + Category = "cat", + Label = "label", + Value = 22 + }); + } + } + + public async ValueTask DisposeAsync() + { + if(_googleAnalytincsService is not null) + { + await _googleAnalytincsService.DisposeAsync(); } } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor index 2f4eab97..e9a55e4f 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor @@ -14,7 +14,7 @@ </div> </div> -@*Permalink*@ +@*Permalink initialize*@ @using Majorsoft.Blazor.Components.PermaLink @using Microsoft.Extensions.Logging @using Majorsoft.Blazor.Components.Common.JsInterop.Scroll @@ -23,10 +23,13 @@ @inject NavigationManager _navigationManager @inject ILogger<IPermaLinkWatcherService> _logger -@*Server hoster Blazor console log*@ +@*Server hosted Blazor console log*@ @using Majorsoft.Blazor.Server.Logging.Console @inject IBrowserConsoleLoggerService _browserConsoleLogger +@* Google Analytics *@ +@inject IGoogleAnalyticsService _googleAnalytincsService + @implements IDisposable @implements IAsyncDisposable @@ -43,6 +46,9 @@ //setup console log await _browserConsoleLogger.StartLoggerAsync(); + + //setup Google analitics + await _googleAnalytincsService.InitializeAsync("G-1QD2VGTEWX"); } } @@ -57,5 +63,10 @@ { await _browserConsoleLogger.DisposeAsync(); } + + if(_googleAnalytincsService is not null) + { + await _googleAnalytincsService.DisposeAsync(); + } } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs b/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs index 4456c637..aff9b45b 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs @@ -37,7 +37,7 @@ public void ConfigureServices(IServiceCollection services) services.AddMapExtensions(); services.AddBrowserStorage(); - services.AddGoogleAnalytics(); + services.AddGoogleAnalyticsForBlazorServer(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/_Imports.razor b/src/Majorsoft.Blazor.Components.TestServerApp/_Imports.razor index 31bebc3b..26c534e7 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.TestServerApp/_Imports.razor @@ -11,4 +11,6 @@ @using Majorsoft.Blazor.Components.TestApps.Common @using Majorsoft.Blazor.Components.TestApps.Common.Components -@using Majorsoft.Blazor.Components.TestApps.Common.Shared \ No newline at end of file +@using Majorsoft.Blazor.Components.TestApps.Common.Shared + +@using Majorsoft.Blazor.Extensions.Analytics.Google \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs index f3eaf7a1..89e3373d 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs @@ -11,19 +11,30 @@ public static class AnalyticsExtension { /// <summary> /// Registers required Google Analytic services into IServiceCollection + /// DO NOT CALL with <see cref="trackingId"/> value in case of Blazor Server! /// </summary> /// <param name="services">IServiceCollection instance</param> - /// <param name="trackingId">Google Tracking Id when provided <see cref="IGoogleAnalyticsService.Initialize(string)"/> will be called. DO NOT CALL with value in case of Blazor Server!</param> + /// <param name="trackingId">Google Tracking Id when provided <see cref="IGoogleAnalyticsService.InitializeAsync(string)"/> will be called. DO NOT CALL with value in case of Blazor Server!</param> /// <returns>IServiceCollection</returns> public static IServiceCollection AddGoogleAnalytics(this IServiceCollection services, string trackingId = "") { services.AddTransient<IGoogleAnalyticsService, GoogleAnalyticsService>(); if (!string.IsNullOrWhiteSpace(trackingId)) { - services.BuildServiceProvider().GetRequiredService<IGoogleAnalyticsService>()?.Initialize(trackingId); + services.BuildServiceProvider().GetRequiredService<IGoogleAnalyticsService>()?.InitializeAsync(trackingId); } return services; } + + /// <summary> + /// Registers required Google Analytic services into IServiceCollection for Blazor Server + /// </summary> + /// <param name="services">IServiceCollection instance</param> + /// <returns>IServiceCollection</returns> + public static IServiceCollection AddGoogleAnalyticsForBlazorServer(this IServiceCollection services) + { + return services.AddGoogleAnalytics(); + } } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsCustomEventArgs.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsCustomEventArgs.cs new file mode 100644 index 00000000..da782dfe --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsCustomEventArgs.cs @@ -0,0 +1,29 @@ +namespace Majorsoft.Blazor.Extensions.Analytics.Google +{ + /// <summary> + /// Data for custom event. + /// For more details see: https://developers.google.com/analytics/devguides/collection/gtagjs/events + /// </summary> + public class GoogleAnalyticsCustomEventArgs + { + /// <summary> + /// The value that will appear as the event action in Google Analytics Event reports. + /// </summary> + public string Action { get; set; } + + /// <summary> + /// The category of the event. + /// </summary> + public string Category { get; set; } + + /// <summary> + /// The label of the event. + /// </summary> + public string Label { get; set; } + + /// <summary> + /// A non-negative integer that will appear as the event value. + /// </summary> + public int Value { get; set; } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsEventTypes.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsEventTypes.cs new file mode 100644 index 00000000..46d22712 --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsEventTypes.cs @@ -0,0 +1,33 @@ +namespace Majorsoft.Blazor.Extensions.Analytics.Google +{ + /// <summary> + /// Event types for Google Analytics + /// for event and parameter details see: https://developers.google.com/gtagjs/reference/event + /// </summary> + public enum GoogleAnalyticsEventTypes + { + add_payment_info, + add_to_cart, + add_to_wishlist, + begin_checkout, + checkout_progress, + exception, + generate_lead, + login, + page_view, + purchase, + refund, + remove_from_cart, + screen_view, + search, + select_content, + set_checkout_option, + share, + sign_up, + timing_complete, + view_item, + view_item_list, + view_promotion, + view_search_results + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs index 81206d26..73a35280 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs @@ -14,7 +14,7 @@ namespace Majorsoft.Blazor.Extensions.Analytics.Google public interface IGoogleAnalyticsService : IAsyncDisposable { /// <summary> - /// Google analytics uniquely Id which was used in <see cref="Initialize(string)"/> method. + /// Google analytics uniquely Id which was used in <see cref="InitializeAsync(string)"/> method. /// </summary> string TrackingId { get; } @@ -23,7 +23,7 @@ public interface IGoogleAnalyticsService : IAsyncDisposable /// </summary> /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> /// <returns>Async ValueTask</returns> - ValueTask Initialize(string trackingId); + ValueTask InitializeAsync(string trackingId); /// <summary> /// Allows you to add additional configuration information to targets. This is typically product-specific configuration for a product @@ -32,7 +32,7 @@ public interface IGoogleAnalyticsService : IAsyncDisposable /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> /// <param name="configInfo">Is one or more optional parameter-value pairs</param> /// <returns>Async ValueTask</returns> - ValueTask Config(string trackingId = "", Dictionary<string, object>? configInfo = null); + ValueTask ConfigAsync(string trackingId = "", Dictionary<string, object>? configInfo = null); /// <summary> /// Allows you to get various values from gtag.js including values set with the set command. @@ -40,22 +40,30 @@ public interface IGoogleAnalyticsService : IAsyncDisposable /// <param name="fieldName">The name of the field to get.</param> /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> /// <returns>Async ValueTask</returns> - ValueTask Get(string fieldName, string trackingId = ""); + ValueTask GetAsync(string fieldName, string trackingId = ""); /// <summary> /// Allows you to set values that persist across all the subsequent gtag() calls on the page. /// </summary> /// <param name="parameters">Is a key name and the value that is to persist across gtag() calls.</param> /// <returns>Async ValueTask</returns> - ValueTask Set(ExpandoObject parameters); + ValueTask SetAsync(ExpandoObject parameters); /// <summary> /// Use the event command to send event data. /// </summary> - /// <param name="eventName">A recommended event or a custom event name.</param> + /// <param name="eventType">A recommended event</param> /// <param name="eventParams">Is one or more parameter-value pairs.</param> /// <returns>Async ValueTask</returns> - ValueTask Event(string eventName, Dictionary<string, object> eventParams); + ValueTask EventAsync(GoogleAnalyticsEventTypes eventType, ExpandoObject eventParams); + + /// <summary> + /// Use the event command to send custom event data. + /// </summary> + /// <param name="customEventName">Custom event name.</param> + /// <param name="eventData">Custom event data</param> + /// <returns>Async ValueTask</returns> + ValueTask CustomEventAsync(string customEventName, GoogleAnalyticsCustomEventArgs eventData); } /// <summary> @@ -64,8 +72,9 @@ public interface IGoogleAnalyticsService : IAsyncDisposable public class GoogleAnalyticsService : IGoogleAnalyticsService { private readonly Lazy<Task<IJSObjectReference>> moduleTask; + private static string _trackingId; //Service cannot registered as Singleton. - public string TrackingId { get; private set; } + public string TrackingId => _trackingId; public GoogleAnalyticsService(IJSRuntime jsRuntime) { @@ -79,38 +88,44 @@ public GoogleAnalyticsService(IJSRuntime jsRuntime) moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>("import", js).AsTask()); } - public async ValueTask Initialize(string trackingId) + public async ValueTask InitializeAsync(string trackingId) { if(!string.IsNullOrWhiteSpace(TrackingId) || string.IsNullOrWhiteSpace(trackingId)) //Already initializer or wrong id { return; } - TrackingId = trackingId; + _trackingId = trackingId; var module = await moduleTask.Value; await module.InvokeVoidAsync("init", trackingId); } - public async ValueTask Config(string trackingId = "", Dictionary<string, object>? configInfo = null) + public async ValueTask ConfigAsync(string trackingId = "", Dictionary<string, object>? configInfo = null) { var module = await moduleTask.Value; await module.InvokeVoidAsync("config", string.IsNullOrWhiteSpace(trackingId) ? TrackingId : trackingId, configInfo?.ToList()); } - public async ValueTask Get(string fieldName, string trackingId = "") + public async ValueTask GetAsync(string fieldName, string trackingId = "") { var module = await moduleTask.Value; await module.InvokeVoidAsync("get", string.IsNullOrWhiteSpace(trackingId) ? TrackingId : trackingId, fieldName); //TODO: callback results } - public async ValueTask Set(ExpandoObject parameters) + public async ValueTask SetAsync(ExpandoObject parameters) { var module = await moduleTask.Value; await module.InvokeVoidAsync("set", parameters); } - public async ValueTask Event(string eventName, Dictionary<string, object> eventParams) + public async ValueTask EventAsync(GoogleAnalyticsEventTypes eventType, ExpandoObject eventParams) + { + var module = await moduleTask.Value; + await module.InvokeVoidAsync("event", eventType.ToString(), eventParams); + } + + public async ValueTask CustomEventAsync(string customEventName, GoogleAnalyticsCustomEventArgs eventData) { var module = await moduleTask.Value; - await module.InvokeVoidAsync("event", eventName, eventParams?.ToList()); + await module.InvokeVoidAsync("customEvent", customEventName, eventData); } public async ValueTask DisposeAsync() diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml index bbd86d39..16b8d887 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml @@ -12,11 +12,51 @@ <member name="M:Majorsoft.Blazor.Extensions.Analytics.AnalyticsExtension.AddGoogleAnalytics(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.String)"> <summary> Registers required Google Analytic services into IServiceCollection + DO NOT CALL with <see cref="!:trackingId"/> value in case of Blazor Server! </summary> <param name="services">IServiceCollection instance</param> - <param name="trackingId">Google Tracking Id when provided <see cref="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Initialize(System.String)"/> will be called. DO NOT CALL with value in case of Blazor Server!</param> + <param name="trackingId">Google Tracking Id when provided <see cref="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.InitializeAsync(System.String)"/> will be called. DO NOT CALL with value in case of Blazor Server!</param> <returns>IServiceCollection</returns> </member> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.AnalyticsExtension.AddGoogleAnalyticsForBlazorServer(Microsoft.Extensions.DependencyInjection.IServiceCollection)"> + <summary> + Registers required Google Analytic services into IServiceCollection for Blazor Server + </summary> + <param name="services">IServiceCollection instance</param> + <returns>IServiceCollection</returns> + </member> + <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsCustomEventArgs"> + <summary> + Data for custom event. + For more details see: https://developers.google.com/analytics/devguides/collection/gtagjs/events + </summary> + </member> + <member name="P:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsCustomEventArgs.Action"> + <summary> + The value that will appear as the event action in Google Analytics Event reports. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsCustomEventArgs.Category"> + <summary> + The category of the event. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsCustomEventArgs.Label"> + <summary> + The label of the event. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsCustomEventArgs.Value"> + <summary> + A non-negative integer that will appear as the event value. + </summary> + </member> + <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsEventTypes"> + <summary> + Event types for Google Analytics + for event and parameter details see: https://developers.google.com/gtagjs/reference/event + </summary> + </member> <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService"> <summary> Injectable service to handle Google analytics gtag.js. @@ -24,17 +64,17 @@ </member> <member name="P:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.TrackingId"> <summary> - Google analytics uniquely Id which was used in <see cref="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Initialize(System.String)"/> method. + Google analytics uniquely Id which was used in <see cref="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.InitializeAsync(System.String)"/> method. </summary> </member> - <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Initialize(System.String)"> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.InitializeAsync(System.String)"> <summary> Initialize Google analytics by registering gtag.js to the HTML document. Should be called once. </summary> <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> <returns>Async ValueTask</returns> </member> - <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Config(System.String,System.Collections.Generic.Dictionary{System.String,System.Object})"> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.ConfigAsync(System.String,System.Collections.Generic.Dictionary{System.String,System.Object})"> <summary> Allows you to add additional configuration information to targets. This is typically product-specific configuration for a product such as Google Ads or Google Analytics. @@ -43,7 +83,7 @@ <param name="configInfo">Is one or more optional parameter-value pairs</param> <returns>Async ValueTask</returns> </member> - <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Get(System.String,System.String)"> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.GetAsync(System.String,System.String)"> <summary> Allows you to get various values from gtag.js including values set with the set command. </summary> @@ -51,21 +91,29 @@ <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> <returns>Async ValueTask</returns> </member> - <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Set(System.Dynamic.ExpandoObject)"> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.SetAsync(System.Dynamic.ExpandoObject)"> <summary> Allows you to set values that persist across all the subsequent gtag() calls on the page. </summary> <param name="parameters">Is a key name and the value that is to persist across gtag() calls.</param> <returns>Async ValueTask</returns> </member> - <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.Event(System.String,System.Collections.Generic.Dictionary{System.String,System.Object})"> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.EventAsync(Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsEventTypes,System.Dynamic.ExpandoObject)"> <summary> Use the event command to send event data. </summary> - <param name="eventName">A recommended event or a custom event name.</param> + <param name="eventType">A recommended event</param> <param name="eventParams">Is one or more parameter-value pairs.</param> <returns>Async ValueTask</returns> </member> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.CustomEventAsync(System.String,Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsCustomEventArgs)"> + <summary> + Use the event command to send custom event data. + </summary> + <param name="customEventName">Custom event name.</param> + <param name="eventData">Custom event data</param> + <returns>Async ValueTask</returns> + </member> <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsService"> <summary> Implementation of <see cref="T:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService"/> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js index a5ddd969..8c325b8f 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js @@ -62,4 +62,14 @@ export function event(eventName, data) { if (window.gtag && eventName && data) { gtag('event', eventName, data); } +} +export function customEvent(eventName, data) { + if (window.gtag && eventName && data) { + gtag('event', eventName, { + 'event_action': data.Action, + 'event_category': data.Category, + 'event_label': data.Label, + 'value': data.Value + }); + } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js index 0a1a89f5..e4a28637 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js @@ -2,4 +2,4 @@ export function init(n){if(n){let i="https://www.googletagmanager.com/gtag/js?id function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); - gtag('config', '{0}');`.replace("{0}",n);document.head.appendChild(u)}}}export function config(n,t){n&&window.gtag&>ag("config",n,t)}export function get(n,t){n&&window.gtag&>ag("get",n,t,n=>{console.log(n)})}export function set(n){window.gtag&&n&>ag("set",n)}export function event(n,t){window.gtag&&n&&t&>ag("event",n,t)} \ No newline at end of file + gtag('config', '{0}');`.replace("{0}",n);document.head.appendChild(u)}}}export function config(n,t){n&&window.gtag&>ag("config",n,t)}export function get(n,t){n&&window.gtag&>ag("get",n,t,n=>{console.log(n)})}export function set(n){window.gtag&&n&>ag("set",n)}export function event(n,t){window.gtag&&n&&t&>ag("event",n,t)}export function customEvent(n,t){window.gtag&&n&&t&>ag("event",n,{event_action:t.Action,event_category:t.Category,event_label:t.Label,value:t.Value})} \ No newline at end of file From b8fa9f993f84a5671a812742ef07e66152b6d446 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 21 Jun 2021 22:13:21 +0200 Subject: [PATCH 34/69] Finished Google Analytics features. --- .../Components/GoogleAnalytics.razor | 14 ++-- .../_Imports.razor | 1 + .../Google/GoogleAnalyticsGetEventInfo.cs | 26 +++++++ .../Google/GoogleAnalyticsService.cs | 77 ++++--------------- .../Google/IGoogleAnalyticsService.cs | 65 ++++++++++++++++ .../Majorsoft.Blazor.Extensions.Analytics.xml | 20 +++-- .../wwwroot/googleAnalytics.js | 6 +- .../wwwroot/googleAnalytics.min.js | 2 +- 8 files changed, 134 insertions(+), 77 deletions(-) create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsGetEventInfo.cs create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/Google/IGoogleAnalyticsService.cs diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor index cd4eec9f..ea523a14 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor @@ -25,12 +25,18 @@ <strong><code>IGoogleAnalyticsService</code> is an injectable service</strong> for enabling <a href="https://support.google.com/analytics/answer/1008015?hl=en#" target="_blank">Google Analytics</a> page tracking in Blazor Apps. </p> + + <p> + <strong>To see how Majorsoft.Blazor.Extensions.Analytics works please check the demo code. And Google + <a href="https://developers.google.com/gtagjs/reference/api#get">analytics features</a>.</strong> + </p> </div> </div> @using System.Dynamic @inject IGoogleAnalyticsService _googleAnalytincsService +@inject ILogger<GoogleAnalytics> _logger @implements IAsyncDisposable @code { @@ -38,19 +44,17 @@ { if(firstRender) { - /*var sessionId = */ - await _googleAnalytincsService.GetAsync("session_id"); + await _googleAnalytincsService.GetAsync("session_id", async (res) => { _logger.LogInformation($"Google analytics Get result: {res}"); }); //custom set/get dynamic exp = new ExpandoObject(); exp.test = 27; await _googleAnalytincsService.SetAsync(exp); - /*var test =*/ - await _googleAnalytincsService.GetAsync("test"); + await _googleAnalytincsService.GetAsync("test", async (res) => { _logger.LogInformation($"Google analytics Get result: {res}"); }); dynamic exp2 = new ExpandoObject(); - exp2.search_term = "heeeeeeeeeeee"; + exp2.search_term = "Searching custom event..."; await _googleAnalytincsService.EventAsync(GoogleAnalyticsEventTypes.search, exp2); await _googleAnalytincsService.CustomEventAsync("testEvent", new GoogleAnalyticsCustomEventArgs() diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor index 5702ed14..3340f34a 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor @@ -2,6 +2,7 @@ @using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components @using Microsoft.AspNetCore.Components.Routing +@using Microsoft.Extensions.Logging @using System.ComponentModel.DataAnnotations @using System.Collections.ObjectModel diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsGetEventInfo.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsGetEventInfo.cs new file mode 100644 index 00000000..094f9b80 --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsGetEventInfo.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; + +using Microsoft.JSInterop; + +namespace Majorsoft.Blazor.Extensions.Analytics.Google +{ + /// <summary> + /// Google analytics Get response event <see cref="DotNetObjectReference"/> info to handle JS callback + /// </summary> + internal sealed class GoogleAnalyticsGetEventInfo + { + private readonly Func<object, Task> _googleAnalyticsGetEventCallback; + + public GoogleAnalyticsGetEventInfo(Func<object, Task> googleAnalyticsGetEventCallback) + { + _googleAnalyticsGetEventCallback = googleAnalyticsGetEventCallback; + } + + [JSInvokable("GoogleAnalyticsResult")] + public async Task TransitionEvent(object args) + { + await _googleAnalyticsGetEventCallback(args); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs index 73a35280..e52ddcfb 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs @@ -8,69 +8,12 @@ namespace Majorsoft.Blazor.Extensions.Analytics.Google { - /// <summary> - /// Injectable service to handle Google analytics gtag.js. - /// </summary> - public interface IGoogleAnalyticsService : IAsyncDisposable - { - /// <summary> - /// Google analytics uniquely Id which was used in <see cref="InitializeAsync(string)"/> method. - /// </summary> - string TrackingId { get; } - - /// <summary> - /// Initialize Google analytics by registering gtag.js to the HTML document. Should be called once. - /// </summary> - /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> - /// <returns>Async ValueTask</returns> - ValueTask InitializeAsync(string trackingId); - - /// <summary> - /// Allows you to add additional configuration information to targets. This is typically product-specific configuration for a product - /// such as Google Ads or Google Analytics. - /// </summary> - /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> - /// <param name="configInfo">Is one or more optional parameter-value pairs</param> - /// <returns>Async ValueTask</returns> - ValueTask ConfigAsync(string trackingId = "", Dictionary<string, object>? configInfo = null); - - /// <summary> - /// Allows you to get various values from gtag.js including values set with the set command. - /// </summary> - /// <param name="fieldName">The name of the field to get.</param> - /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> - /// <returns>Async ValueTask</returns> - ValueTask GetAsync(string fieldName, string trackingId = ""); - - /// <summary> - /// Allows you to set values that persist across all the subsequent gtag() calls on the page. - /// </summary> - /// <param name="parameters">Is a key name and the value that is to persist across gtag() calls.</param> - /// <returns>Async ValueTask</returns> - ValueTask SetAsync(ExpandoObject parameters); - - /// <summary> - /// Use the event command to send event data. - /// </summary> - /// <param name="eventType">A recommended event</param> - /// <param name="eventParams">Is one or more parameter-value pairs.</param> - /// <returns>Async ValueTask</returns> - ValueTask EventAsync(GoogleAnalyticsEventTypes eventType, ExpandoObject eventParams); - - /// <summary> - /// Use the event command to send custom event data. - /// </summary> - /// <param name="customEventName">Custom event name.</param> - /// <param name="eventData">Custom event data</param> - /// <returns>Async ValueTask</returns> - ValueTask CustomEventAsync(string customEventName, GoogleAnalyticsCustomEventArgs eventData); - } - /// <summary> /// Implementation of <see cref="IGoogleAnalyticsService"/> /// </summary> public class GoogleAnalyticsService : IGoogleAnalyticsService { + private List<DotNetObjectReference<GoogleAnalyticsGetEventInfo>> _dotNetObjectReferences; private readonly Lazy<Task<IJSObjectReference>> moduleTask; private static string _trackingId; //Service cannot registered as Singleton. @@ -86,6 +29,7 @@ public GoogleAnalyticsService(IJSRuntime jsRuntime) #endif moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>("import", js).AsTask()); + _dotNetObjectReferences = new List<DotNetObjectReference<GoogleAnalyticsGetEventInfo>>(); } public async ValueTask InitializeAsync(string trackingId) @@ -101,15 +45,21 @@ public async ValueTask InitializeAsync(string trackingId) await module.InvokeVoidAsync("init", trackingId); } - public async ValueTask ConfigAsync(string trackingId = "", Dictionary<string, object>? configInfo = null) + public async ValueTask ConfigAsync(string trackingId = "", ExpandoObject? configInfo = null) { var module = await moduleTask.Value; await module.InvokeVoidAsync("config", string.IsNullOrWhiteSpace(trackingId) ? TrackingId : trackingId, configInfo?.ToList()); } - public async ValueTask GetAsync(string fieldName, string trackingId = "") + public async ValueTask GetAsync(string fieldName, Func<object, Task> callback, string trackingId = "") { var module = await moduleTask.Value; - await module.InvokeVoidAsync("get", string.IsNullOrWhiteSpace(trackingId) ? TrackingId : trackingId, fieldName); //TODO: callback results + + var info = new GoogleAnalyticsGetEventInfo(callback); + var dotnetRef = DotNetObjectReference.Create<GoogleAnalyticsGetEventInfo>(info); + + _dotNetObjectReferences.Add(dotnetRef); + + await module.InvokeVoidAsync("get", string.IsNullOrWhiteSpace(trackingId) ? TrackingId : trackingId, fieldName, dotnetRef); } public async ValueTask SetAsync(ExpandoObject parameters) { @@ -135,6 +85,11 @@ public async ValueTask DisposeAsync() var module = await moduleTask.Value; await module.DisposeAsync(); } + + foreach (var item in _dotNetObjectReferences) + { + item.Dispose(); + } } } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/IGoogleAnalyticsService.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/IGoogleAnalyticsService.cs new file mode 100644 index 00000000..9cfaa5dc --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/IGoogleAnalyticsService.cs @@ -0,0 +1,65 @@ +using System; +using System.Dynamic; +using System.Threading.Tasks; + +namespace Majorsoft.Blazor.Extensions.Analytics.Google +{ + /// <summary> + /// Injectable service to handle Google analytics gtag.js. + /// </summary> + public interface IGoogleAnalyticsService : IAsyncDisposable + { + /// <summary> + /// Google analytics uniquely Id which was used in <see cref="InitializeAsync(string)"/> method. + /// </summary> + string TrackingId { get; } + + /// <summary> + /// Initialize Google analytics by registering gtag.js to the HTML document. Should be called once. + /// </summary> + /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> + /// <returns>Async ValueTask</returns> + ValueTask InitializeAsync(string trackingId); + + /// <summary> + /// Allows you to add additional configuration information to targets. This is typically product-specific configuration for a product + /// such as Google Ads or Google Analytics. + /// </summary> + /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> + /// <param name="configInfo">Is one or more optional parameter-value pairs</param> + /// <returns>Async ValueTask</returns> + ValueTask ConfigAsync(string trackingId = "", ExpandoObject? configInfo = null); + + /// <summary> + /// Allows you to get various values from gtag.js including values set with the set command. + /// </summary> + /// <param name="fieldName">The name of the field to get.</param> + /// <param name="callback">A function that will be invoked with the requested field, or undefined if it is unset.</param> + /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> + /// <returns>Async ValueTask</returns> + ValueTask GetAsync(string fieldName, Func<object, Task> callback, string trackingId = ""); + + /// <summary> + /// Allows you to set values that persist across all the subsequent gtag() calls on the page. + /// </summary> + /// <param name="parameters">Is a key name and the value that is to persist across gtag() calls.</param> + /// <returns>Async ValueTask</returns> + ValueTask SetAsync(ExpandoObject parameters); + + /// <summary> + /// Use the event command to send event data. + /// </summary> + /// <param name="eventType">A recommended event</param> + /// <param name="eventParams">Is one or more parameter-value pairs.</param> + /// <returns>Async ValueTask</returns> + ValueTask EventAsync(GoogleAnalyticsEventTypes eventType, ExpandoObject eventParams); + + /// <summary> + /// Use the event command to send custom event data. + /// </summary> + /// <param name="customEventName">Custom event name.</param> + /// <param name="eventData">Custom event data</param> + /// <returns>Async ValueTask</returns> + ValueTask CustomEventAsync(string customEventName, GoogleAnalyticsCustomEventArgs eventData); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml index 16b8d887..4b17a8e4 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml @@ -57,6 +57,16 @@ for event and parameter details see: https://developers.google.com/gtagjs/reference/event </summary> </member> + <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsGetEventInfo"> + <summary> + Google analytics Get response event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback + </summary> + </member> + <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsService"> + <summary> + Implementation of <see cref="T:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService"/> + </summary> + </member> <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService"> <summary> Injectable service to handle Google analytics gtag.js. @@ -74,7 +84,7 @@ <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> <returns>Async ValueTask</returns> </member> - <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.ConfigAsync(System.String,System.Collections.Generic.Dictionary{System.String,System.Object})"> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.ConfigAsync(System.String,System.Dynamic.ExpandoObject)"> <summary> Allows you to add additional configuration information to targets. This is typically product-specific configuration for a product such as Google Ads or Google Analytics. @@ -83,11 +93,12 @@ <param name="configInfo">Is one or more optional parameter-value pairs</param> <returns>Async ValueTask</returns> </member> - <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.GetAsync(System.String,System.String)"> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.GetAsync(System.String,System.Func{System.Object,System.Threading.Tasks.Task},System.String)"> <summary> Allows you to get various values from gtag.js including values set with the set command. </summary> <param name="fieldName">The name of the field to get.</param> + <param name="callback">A function that will be invoked with the requested field, or undefined if it is unset.</param> <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> <returns>Async ValueTask</returns> </member> @@ -114,10 +125,5 @@ <param name="eventData">Custom event data</param> <returns>Async ValueTask</returns> </member> - <member name="T:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsService"> - <summary> - Implementation of <see cref="T:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService"/> - </summary> - </member> </members> </doc> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js index 8c325b8f..7aa43aac 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js @@ -45,11 +45,11 @@ export function config(trackingId, data) { gtag('config', trackingId, data); } } -export function get(trackingId, fieldName) { +export function get(trackingId, fieldName, dotnetRef) { if (trackingId && window.gtag) { gtag('get', trackingId, fieldName, (result) => { - //TOOD: callback method here... - console.log(result); + dotnetRef.invokeMethodAsync("GoogleAnalyticsResult", result); + //console.log(result); }); } } diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js index e4a28637..f8f74cf8 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.min.js @@ -2,4 +2,4 @@ export function init(n){if(n){let i="https://www.googletagmanager.com/gtag/js?id function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); - gtag('config', '{0}');`.replace("{0}",n);document.head.appendChild(u)}}}export function config(n,t){n&&window.gtag&>ag("config",n,t)}export function get(n,t){n&&window.gtag&>ag("get",n,t,n=>{console.log(n)})}export function set(n){window.gtag&&n&>ag("set",n)}export function event(n,t){window.gtag&&n&&t&>ag("event",n,t)}export function customEvent(n,t){window.gtag&&n&&t&>ag("event",n,{event_action:t.Action,event_category:t.Category,event_label:t.Label,value:t.Value})} \ No newline at end of file + gtag('config', '{0}');`.replace("{0}",n);document.head.appendChild(u)}}}export function config(n,t){n&&window.gtag&>ag("config",n,t)}export function get(n,t,i){n&&window.gtag&>ag("get",n,t,n=>{i.invokeMethodAsync("GoogleAnalyticsResult",n)})}export function set(n){window.gtag&&n&>ag("set",n)}export function event(n,t){window.gtag&&n&&t&>ag("event",n,t)}export function customEvent(n,t){window.gtag&&n&&t&>ag("event",n,{event_action:t.Action,event_category:t.Category,event_label:t.Label,value:t.Value})} \ No newline at end of file From 5b77a790ff1d917e97b6e6d7c72d95cef9985fbe Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Tue, 22 Jun 2021 08:21:01 +0200 Subject: [PATCH 35/69] Simplify and unify Google analytics initialization. --- .../Program.cs | 2 +- .../Shared/MainLayout.razor | 4 +++ .../Shared/MainLayout.razor | 13 +++------ .../Startup.cs | 2 +- .../AnalyticsExtension.cs | 19 +------------ .../Google/GoogleAnalyticsInitializer.razor | 27 +++++++++++++++++++ .../Majorsoft.Blazor.Extensions.Analytics.xml | 16 +++++------ .../wwwroot/googleAnalytics.js | 1 - 8 files changed, 43 insertions(+), 41 deletions(-) create mode 100644 src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsInitializer.razor diff --git a/src/Majorsoft.Blazor.Components.TestApp/Program.cs b/src/Majorsoft.Blazor.Components.TestApp/Program.cs index bbdbcf61..6ac84a0b 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/Program.cs +++ b/src/Majorsoft.Blazor.Components.TestApp/Program.cs @@ -29,7 +29,7 @@ public static async Task Main(string[] args) builder.Services.AddMapExtensions(); builder.Services.AddBrowserStorage(); - builder.Services.AddGoogleAnalytics("G-1QD2VGTEWX"); + builder.Services.AddGoogleAnalytics(); builder.Logging.AddBrowserConsole() .SetMinimumLevel(LogLevel.Debug).AddFilter("Microsoft", LogLevel.Information); diff --git a/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor b/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor index 51ef1511..ba1aaea7 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor +++ b/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor @@ -16,6 +16,10 @@ </div> </div> +@* Google Analytics initialize*@ +@using Majorsoft.Blazor.Extensions.Analytics.Google +<GoogleAnalyticsInitializer TrackingId="G-1QD2VGTEWX" /> + @using Majorsoft.Blazor.Components.PermaLink @inject IPermaLinkWatcherService _permalinkWatcher @implements IDisposable diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor index e9a55e4f..77e894ea 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor @@ -27,8 +27,9 @@ @using Majorsoft.Blazor.Server.Logging.Console @inject IBrowserConsoleLoggerService _browserConsoleLogger -@* Google Analytics *@ -@inject IGoogleAnalyticsService _googleAnalytincsService +@* Google Analytics initialize*@ +@using Majorsoft.Blazor.Extensions.Analytics.Google +<GoogleAnalyticsInitializer TrackingId="G-1QD2VGTEWX" /> @implements IDisposable @implements IAsyncDisposable @@ -46,9 +47,6 @@ //setup console log await _browserConsoleLogger.StartLoggerAsync(); - - //setup Google analitics - await _googleAnalytincsService.InitializeAsync("G-1QD2VGTEWX"); } } @@ -63,10 +61,5 @@ { await _browserConsoleLogger.DisposeAsync(); } - - if(_googleAnalytincsService is not null) - { - await _googleAnalytincsService.DisposeAsync(); - } } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs b/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs index aff9b45b..4456c637 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs @@ -37,7 +37,7 @@ public void ConfigureServices(IServiceCollection services) services.AddMapExtensions(); services.AddBrowserStorage(); - services.AddGoogleAnalyticsForBlazorServer(); + services.AddGoogleAnalytics(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs index 89e3373d..51be9b4e 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/AnalyticsExtension.cs @@ -11,30 +11,13 @@ public static class AnalyticsExtension { /// <summary> /// Registers required Google Analytic services into IServiceCollection - /// DO NOT CALL with <see cref="trackingId"/> value in case of Blazor Server! /// </summary> /// <param name="services">IServiceCollection instance</param> - /// <param name="trackingId">Google Tracking Id when provided <see cref="IGoogleAnalyticsService.InitializeAsync(string)"/> will be called. DO NOT CALL with value in case of Blazor Server!</param> /// <returns>IServiceCollection</returns> - public static IServiceCollection AddGoogleAnalytics(this IServiceCollection services, string trackingId = "") + public static IServiceCollection AddGoogleAnalytics(this IServiceCollection services) { services.AddTransient<IGoogleAnalyticsService, GoogleAnalyticsService>(); - if (!string.IsNullOrWhiteSpace(trackingId)) - { - services.BuildServiceProvider().GetRequiredService<IGoogleAnalyticsService>()?.InitializeAsync(trackingId); - } - return services; } - - /// <summary> - /// Registers required Google Analytic services into IServiceCollection for Blazor Server - /// </summary> - /// <param name="services">IServiceCollection instance</param> - /// <returns>IServiceCollection</returns> - public static IServiceCollection AddGoogleAnalyticsForBlazorServer(this IServiceCollection services) - { - return services.AddGoogleAnalytics(); - } } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsInitializer.razor b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsInitializer.razor new file mode 100644 index 00000000..6907157d --- /dev/null +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsInitializer.razor @@ -0,0 +1,27 @@ +@inject IGoogleAnalyticsService _googleAnalytincsService + +@implements IAsyncDisposable + +@code { + /// <summary> + /// Google Analytics Tracking id + /// </summary> + [Parameter] public string TrackingId { get; set; } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + //Initialize Google Analytics once + await _googleAnalytincsService.InitializeAsync(TrackingId); + } + } + + public async ValueTask DisposeAsync() + { + if (_googleAnalytincsService is not null) + { + await _googleAnalytincsService.DisposeAsync(); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml index 4b17a8e4..a5e176d2 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml @@ -9,18 +9,9 @@ Extension methods to register required Analytic services into IServiceCollection </summary> </member> - <member name="M:Majorsoft.Blazor.Extensions.Analytics.AnalyticsExtension.AddGoogleAnalytics(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.String)"> + <member name="M:Majorsoft.Blazor.Extensions.Analytics.AnalyticsExtension.AddGoogleAnalytics(Microsoft.Extensions.DependencyInjection.IServiceCollection)"> <summary> Registers required Google Analytic services into IServiceCollection - DO NOT CALL with <see cref="!:trackingId"/> value in case of Blazor Server! - </summary> - <param name="services">IServiceCollection instance</param> - <param name="trackingId">Google Tracking Id when provided <see cref="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.InitializeAsync(System.String)"/> will be called. DO NOT CALL with value in case of Blazor Server!</param> - <returns>IServiceCollection</returns> - </member> - <member name="M:Majorsoft.Blazor.Extensions.Analytics.AnalyticsExtension.AddGoogleAnalyticsForBlazorServer(Microsoft.Extensions.DependencyInjection.IServiceCollection)"> - <summary> - Registers required Google Analytic services into IServiceCollection for Blazor Server </summary> <param name="services">IServiceCollection instance</param> <returns>IServiceCollection</returns> @@ -125,5 +116,10 @@ <param name="eventData">Custom event data</param> <returns>Async ValueTask</returns> </member> + <member name="P:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsInitializer.TrackingId"> + <summary> + Google Analytics Tracking id + </summary> + </member> </members> </doc> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js index 7aa43aac..ce57247b 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js +++ b/src/Majorsoft.Blazor.Extensions.Analytics/wwwroot/googleAnalytics.js @@ -49,7 +49,6 @@ export function get(trackingId, fieldName, dotnetRef) { if (trackingId && window.gtag) { gtag('get', trackingId, fieldName, (result) => { dotnetRef.invokeMethodAsync("GoogleAnalyticsResult", result); - //console.log(result); }); } } From c7a0f249c3d09892dad4100bfa82e885bfcd065d Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Tue, 22 Jun 2021 08:40:39 +0200 Subject: [PATCH 36/69] Component rename. --- README.md | 1 + .../Majorsoft.Blazor.Components.TestApp.csproj | 3 +++ .../Pages/AnalyticsPage.razor | 2 +- .../{GoogleAnalytics.razor => SiteAnalytics.razor} | 6 +++--- .../Pages/AnalyticsPage.razor | 2 +- .../Majorsoft.Blazor.Components.TestServerApp.csproj | 3 +++ .../Pages/AnalyticsPage.razor | 2 +- 7 files changed, 13 insertions(+), 6 deletions(-) rename src/Majorsoft.Blazor.Components.TestApps.Common/Components/{GoogleAnalytics.razor => SiteAnalytics.razor} (88%) diff --git a/README.md b/README.md index 649343f4..27425504 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ Check out our planned components and extensions on the project [Wiki page](https - **Majorsoft.Blazor.Server.Logging.Console**: Enables [Browser console logging](https://github.com/majorimi/blazor-components/blob/master/.github/docs/ServerHostedLogging.md) for Blazor applications using **Server Hosted model**. - **Majorsoft.Blazor.WebAssembly.Logging.Console**: Enables [Browser console logging](https://github.com/majorimi/blazor-components/blob/master/.github/docs/WebAssemblyHostedLogging.md) for Blazor applications using **WebAssembly Hosting model**. - **Majorsoft.Blazor.Extensions.BrowserStorage**: Enables [Browser Local and Session storages and Cookies store](https://github.com/majorimi/blazor-components/blob/master/.github/docs/BrowserStorage.md) access for Blazor applications. +- **Majorsoft.Blazor.Extensions.Analytics**: Enables [Analytics services usage](https://github.com/majorimi/blazor-components/blob/master/.github/docs/Analytics.md) in WebAssemply Apps e.g. Google Analytics, etc. ### **Majorsoft Blazor Components** diff --git a/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj b/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj index de3f4c56..99091612 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj +++ b/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj @@ -43,6 +43,9 @@ </ItemGroup> <ItemGroup> + <Content Update="Pages\AnalyticsPage.razor"> + <ExcludeFromSingleFile>true</ExcludeFromSingleFile> + </Content> <Content Update="Pages\DebounceInputPage.razor"> <ExcludeFromSingleFile>true</ExcludeFromSingleFile> </Content> diff --git a/src/Majorsoft.Blazor.Components.TestApp/Pages/AnalyticsPage.razor b/src/Majorsoft.Blazor.Components.TestApp/Pages/AnalyticsPage.razor index 9850231d..a6cd0ab8 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/Pages/AnalyticsPage.razor +++ b/src/Majorsoft.Blazor.Components.TestApp/Pages/AnalyticsPage.razor @@ -1,3 +1,3 @@ @page "/analytics" -<GoogleAnalytics /> \ No newline at end of file +<SiteAnalytics /> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/SiteAnalytics.razor similarity index 88% rename from src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor rename to src/Majorsoft.Blazor.Components.TestApps.Common/Components/SiteAnalytics.razor index ea523a14..905dbc7b 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GoogleAnalytics.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/SiteAnalytics.razor @@ -3,7 +3,7 @@ <div class="container-fluid p-3 mb-3 border rounded"> <h1>Website Analytics extensions</h1> <p> - Blazor extension that enables WebAssemply site analytics e.g. Google, etc. For usage see source code and docs on + Blazor extension that enables analytics services usage in WebAssemply Apps e.g. Google Analytics, etc. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Analytics.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Extensions.Analytics</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Extensions.Analytics" target="_blank">Nuget</a> </p> @@ -28,7 +28,7 @@ <p> <strong>To see how Majorsoft.Blazor.Extensions.Analytics works please check the demo code. And Google - <a href="https://developers.google.com/gtagjs/reference/api#get">analytics features</a>.</strong> + <a href="https://developers.google.com/gtagjs/reference/api">analytics features</a> since this extension mostly a JS wrapper for <code>gtag JS</code>.</strong> </p> </div> @@ -36,7 +36,7 @@ @using System.Dynamic @inject IGoogleAnalyticsService _googleAnalytincsService -@inject ILogger<GoogleAnalytics> _logger +@inject ILogger<SiteAnalytics> _logger @implements IAsyncDisposable @code { diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/AnalyticsPage.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/AnalyticsPage.razor index 9850231d..a6cd0ab8 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/AnalyticsPage.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/AnalyticsPage.razor @@ -1,3 +1,3 @@ @page "/analytics" -<GoogleAnalytics /> \ No newline at end of file +<SiteAnalytics /> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Majorsoft.Blazor.Components.TestServerApp.csproj b/src/Majorsoft.Blazor.Components.TestServerApp/Majorsoft.Blazor.Components.TestServerApp.csproj index d03986f3..e344d3e8 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Majorsoft.Blazor.Components.TestServerApp.csproj +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Majorsoft.Blazor.Components.TestServerApp.csproj @@ -11,6 +11,9 @@ </ItemGroup> <ItemGroup> + <Content Update="Pages\AnalyticsPage.razor"> + <ExcludeFromSingleFile>true</ExcludeFromSingleFile> + </Content> <Content Update="Pages\DebounceInputPage.razor"> <ExcludeFromSingleFile>true</ExcludeFromSingleFile> </Content> diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Pages/AnalyticsPage.razor b/src/Majorsoft.Blazor.Components.TestServerApp/Pages/AnalyticsPage.razor index 9850231d..a6cd0ab8 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Pages/AnalyticsPage.razor +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Pages/AnalyticsPage.razor @@ -1,3 +1,3 @@ @page "/analytics" -<GoogleAnalytics /> \ No newline at end of file +<SiteAnalytics /> \ No newline at end of file From 4244e2722cdc282f55e7b33c9d0309ffa1c28dfb Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Tue, 22 Jun 2021 19:07:55 +0200 Subject: [PATCH 37/69] Docs page for Google Analytics. --- .github/docs/Analytics.md | 168 ++++++++++++++++++ README.md | 2 +- .../Components/SiteAnalytics.razor | 18 +- .../Google/GoogleAnalyticsInitializer.razor | 4 +- .../Google/GoogleAnalyticsService.cs | 2 +- .../Google/IGoogleAnalyticsService.cs | 1 + .../Majorsoft.Blazor.Extensions.Analytics.xml | 3 +- 7 files changed, 187 insertions(+), 11 deletions(-) create mode 100644 .github/docs/Analytics.md diff --git a/.github/docs/Analytics.md b/.github/docs/Analytics.md new file mode 100644 index 00000000..42dcf152 --- /dev/null +++ b/.github/docs/Analytics.md @@ -0,0 +1,168 @@ +Blazor Components Analytics extension +============ +[![Build Status](https://dev.azure.com/major-soft/GitHub/_apis/build/status/blazor-components/blazor-components-build-check)](https://dev.azure.com/major-soft/GitHub/_build/latest?definitionId=6) +[![Package Version](https://img.shields.io/nuget/v/Majorsoft.Blazor.Components.Analytics?label=Latest%20Version)](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Analytics/) +[![NuGet Downloads](https://img.shields.io/nuget/dt/Majorsoft.Blazor.Components.Analytics?label=Downloads)](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Analytics/) +[![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/majorimi/blazor-components/blob/master/LICENSE) + +# About + +Blazor extension that enables analytics services usage for Blazor applications e.g. Google Analytics, etc. +**All components work with WebAssembly and Server hosted models** (Blazor server side configuration is different). +For code examples [see usage](https://github.com/majorimi/blazor-components/blob/master/src/Majorsoft.Blazor.Components.TestApps.Common/Components/SiteAnalytics.razor). + +You can try it out by using the [demo app](https://blazorextensions.z6.web.core.windows.net/analytics). + +# Services + +- **`Google Analytics`**: is a web analytics service offered by Google that tracks and reports website traffic, etc. inside the Google Marketing Platform. +`IGoogleAnalyticsService` is an injectable service for enabling [Google Analytics](https://support.google.com/analytics/answer/1008015?hl=en#) page tracking in Blazor Apps. +To make the initialization simple use `GoogleAnalyticsInitializer` component in your `MainLayout.razor` page and provide Google Analytics `TrackingId`. + +## `Google Analytics` extension +This is a JS wrapper for web analytics service offered by Google that tracks and reports website traffic, etc. inside the Google Marketing Platform. + +### `PermaLinkElement` component +A convenient wrapper component for `IGoogleAnalyticsService` to make Google Analytics initialize simple. + +#### Properties +- **`TrackingId`**: **`string TrackingId { get; set; }` - Required** <br /> +Google Analytics TrackingId provided on Google Analytics manage page. + +### `IGoogleAnalyticsService` service +Injectable service to handle Google analytics `gtag.js`. + +#### Functions +- **`InitializeAsync()`**: **`ValueTask InitializeAsync(string trackingId)`** <br /> +Initialize Google analytics by registering gtag.js to the HTML document. **Should be called once. + Do not call this method if you used `GoogleAnalyticsInitializer`.** +- **`ConfigAsync()`**: **`ValueTask ConfigAsync(string trackingId = "", ExpandoObject? configInfo = null)`** <br /> +Allows you to add additional configuration information to targets. This is typically product-specific configuration for a product +such as Google Ads or Google Analytics. +- **`GetAsync()`**: **``** <br /> +Allows you to get various values from gtag.js including values set with the set command. +- **`SetAsync()`**: **``** <br /> + Allows you to set values that persist across all the subsequent gtag() calls on the page. +- **`EventAsync()`**: **``** <br /> +Use the event command to send event data. +- **`CustomEventAsync()`**: **``** <br /> +Use the event command to send custom event data. + +# Configuration + +## Installation + +**Majorsoft.Blazor.Components.Analytics** is available on [NuGet](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Analytics/). + +```sh +dotnet add package Majorsoft.Blazor.Components.Analytics +``` +Use the `--version` option to specify a [preview version](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Analytics/absoluteLatest) to install. + +## Usage + +Add using statement to your Blazor <component/page>.razor file. Or globally reference it into `_Imports.razor` file. +``` +@using Majorsoft.Blazor.Components.Analytics +@*Google Analytics*@ +@using Majorsoft.Blazor.Components.Analytics.Google +``` + +#### WebAssembly projects + +**In case of WebAssembly project register services in your `Program.cs` file:** +``` +using Majorsoft.Blazor.Components.Analytics; +... +public static async Task Main(string[] args) +{ + var builder = WebAssemblyHostBuilder.CreateDefault(args); + + //Register service + builder.Services.AddGoogleAnalytics(); +} +``` + +**Use `MainLayout.razor` file for WebAssembly project to initialize Google gtag.js only once:** + +``` +@*Google Analytics initialize*@ +<GoogleAnalyticsInitializer TrackingId="<TrackingId here...>" /> + +@code { +} +``` + +#### Server hosted projects +**In case of Server hosted project register dependency services in your `Startup.cs` file:** + +``` +@using Majorsoft.Blazor.Components.Analytics +... + +public void ConfigureServices(IServiceCollection services) +{ + //Register service + services.AddGoogleAnalytics(); +} +``` + +**Use `MainLayout.razor` file for WebAssembly project to initialize Google gtag.js only once:** +``` +@*Google Analytics initialize*@ +<GoogleAnalyticsInitializer TrackingId="<TrackingId here...>" /> + +@code { +} +``` + +#### Using `IGoogleAnalyticsService` service + +Following code example shows how to Set and Get custom values. Also shows sending events and custom events. +For full features supported by Google Analytics please see [docs page](https://developers.google.com/gtagjs/reference/api). + +``` +@using System.Dynamic +@inject IGoogleAnalyticsService _googleAnalytincsService +@implements IAsyncDisposable + +@code{ + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if(firstRender) + { + await _googleAnalytincsService.GetAsync("session_id", async (res) => { _logger.LogInformation($"Google analytics Get result: {res}"); }); + + //Custom set + dynamic exp = new ExpandoObject(); + exp.test = 27; + + await _googleAnalytincsService.SetAsync(exp); + + //Get cutoms set value + await _googleAnalytincsService.GetAsync("test", async (res) => { _logger.LogInformation($"Google analytics Get result: {res}"); }); + + //Built in Search event usage + dynamic exp2 = new ExpandoObject(); + exp2.search_term = "Searching custom event..."; + await _googleAnalytincsService.EventAsync(GoogleAnalyticsEventTypes.search, exp2); + + //Custom event usage + await _googleAnalytincsService.CustomEventAsync("testEvent", new GoogleAnalyticsCustomEventArgs() + { + Action = "Test action", + Category = "Test category", + Label = "Test label", + Value = 1234 + }); + + + public async ValueTask DisposeAsync() + { + if(_googleAnalytincsService is not null) + { + await _googleAnalytincsService.DisposeAsync(); + } + } +} +``` \ No newline at end of file diff --git a/README.md b/README.md index 27425504..111d3c53 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Check out our planned components and extensions on the project [Wiki page](https - **Majorsoft.Blazor.Server.Logging.Console**: Enables [Browser console logging](https://github.com/majorimi/blazor-components/blob/master/.github/docs/ServerHostedLogging.md) for Blazor applications using **Server Hosted model**. - **Majorsoft.Blazor.WebAssembly.Logging.Console**: Enables [Browser console logging](https://github.com/majorimi/blazor-components/blob/master/.github/docs/WebAssemblyHostedLogging.md) for Blazor applications using **WebAssembly Hosting model**. - **Majorsoft.Blazor.Extensions.BrowserStorage**: Enables [Browser Local and Session storages and Cookies store](https://github.com/majorimi/blazor-components/blob/master/.github/docs/BrowserStorage.md) access for Blazor applications. -- **Majorsoft.Blazor.Extensions.Analytics**: Enables [Analytics services usage](https://github.com/majorimi/blazor-components/blob/master/.github/docs/Analytics.md) in WebAssemply Apps e.g. Google Analytics, etc. +- **Majorsoft.Blazor.Extensions.Analytics**: Enables [Analytics services usage](https://github.com/majorimi/blazor-components/blob/master/.github/docs/Analytics.md) for Blazor applications e.g. Google Analytics, etc. ### **Majorsoft Blazor Components** diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/SiteAnalytics.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/SiteAnalytics.razor index 905dbc7b..c1e24e34 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/SiteAnalytics.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/SiteAnalytics.razor @@ -3,7 +3,7 @@ <div class="container-fluid p-3 mb-3 border rounded"> <h1>Website Analytics extensions</h1> <p> - Blazor extension that enables analytics services usage in WebAssemply Apps e.g. Google Analytics, etc. For usage see source code and docs on + Blazor extension that enables analytics services usage for Blazor applications e.g. Google Analytics, etc. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Analytics.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Extensions.Analytics</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Extensions.Analytics" target="_blank">Nuget</a> </p> @@ -24,11 +24,13 @@ <br /> <strong><code>IGoogleAnalyticsService</code> is an injectable service</strong> for enabling <a href="https://support.google.com/analytics/answer/1008015?hl=en#" target="_blank">Google Analytics</a> page tracking in Blazor Apps. + To make the initialization simple use <code>GoogleAnalyticsInitializer</code> component in your <code>MainLayout.razor</code> page and provide Google Analytics TrackingId. </p> <p> <strong>To see how Majorsoft.Blazor.Extensions.Analytics works please check the demo code. And Google - <a href="https://developers.google.com/gtagjs/reference/api">analytics features</a> since this extension mostly a JS wrapper for <code>gtag JS</code>.</strong> + <a href="https://developers.google.com/gtagjs/reference/api">analytics features</a> since this extension mostly a JS wrapper for <code>gtag.js</code>. + </strong> </p> </div> @@ -46,23 +48,27 @@ { await _googleAnalytincsService.GetAsync("session_id", async (res) => { _logger.LogInformation($"Google analytics Get result: {res}"); }); - //custom set/get + //Custom set dynamic exp = new ExpandoObject(); exp.test = 27; await _googleAnalytincsService.SetAsync(exp); + + //Get cutoms set value await _googleAnalytincsService.GetAsync("test", async (res) => { _logger.LogInformation($"Google analytics Get result: {res}"); }); + //Built in Search event usage dynamic exp2 = new ExpandoObject(); exp2.search_term = "Searching custom event..."; await _googleAnalytincsService.EventAsync(GoogleAnalyticsEventTypes.search, exp2); + //Custom event usage await _googleAnalytincsService.CustomEventAsync("testEvent", new GoogleAnalyticsCustomEventArgs() { Action = "Test action", - Category = "cat", - Label = "label", - Value = 22 + Category = "Test category", + Label = "Test label", + Value = 1234 }); } } diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsInitializer.razor b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsInitializer.razor index 6907157d..6d003891 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsInitializer.razor +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsInitializer.razor @@ -4,9 +4,9 @@ @code { /// <summary> - /// Google Analytics Tracking id + /// Google Analytics TrackingId. /// </summary> - [Parameter] public string TrackingId { get; set; } + [Parameter] public string TrackingId { get; set; } = ""; protected override async Task OnAfterRenderAsync(bool firstRender) { diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs index e52ddcfb..9ff5815e 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/GoogleAnalyticsService.cs @@ -15,7 +15,7 @@ public class GoogleAnalyticsService : IGoogleAnalyticsService { private List<DotNetObjectReference<GoogleAnalyticsGetEventInfo>> _dotNetObjectReferences; private readonly Lazy<Task<IJSObjectReference>> moduleTask; - private static string _trackingId; //Service cannot registered as Singleton. + private static string _trackingId = ""; //Service cannot registered as Singleton. public string TrackingId => _trackingId; diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Google/IGoogleAnalyticsService.cs b/src/Majorsoft.Blazor.Extensions.Analytics/Google/IGoogleAnalyticsService.cs index 9cfaa5dc..79762ad7 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Google/IGoogleAnalyticsService.cs +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Google/IGoogleAnalyticsService.cs @@ -16,6 +16,7 @@ public interface IGoogleAnalyticsService : IAsyncDisposable /// <summary> /// Initialize Google analytics by registering gtag.js to the HTML document. Should be called once. + /// Do not call this method if you used <see cref="GoogleAnalyticsInitializer"/>. /// </summary> /// <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> /// <returns>Async ValueTask</returns> diff --git a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml index a5e176d2..c4d5ba03 100644 --- a/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml +++ b/src/Majorsoft.Blazor.Extensions.Analytics/Majorsoft.Blazor.Extensions.Analytics.xml @@ -71,6 +71,7 @@ <member name="M:Majorsoft.Blazor.Extensions.Analytics.Google.IGoogleAnalyticsService.InitializeAsync(System.String)"> <summary> Initialize Google analytics by registering gtag.js to the HTML document. Should be called once. + Do not call this method if you used <see cref="T:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsInitializer"/>. </summary> <param name="trackingId">Is an identifier that uniquely identifies the target for hits, such as a Google Analytics property</param> <returns>Async ValueTask</returns> @@ -118,7 +119,7 @@ </member> <member name="P:Majorsoft.Blazor.Extensions.Analytics.Google.GoogleAnalyticsInitializer.TrackingId"> <summary> - Google Analytics Tracking id + Google Analytics TrackingId. </summary> </member> </members> From 23419112be3a290667c8eb9a5017888f7ebf507a Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Tue, 22 Jun 2021 19:51:02 +0200 Subject: [PATCH 38/69] Created PermalinkWatcher initialize component. --- .../PermaLinkInitializer.razor | 36 +++++++++++++++++++ .../Shared/MainLayout.razor | 17 +++------ .../Shared/MainLayout.razor | 19 +--------- 3 files changed, 41 insertions(+), 31 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.PermaLink/PermaLinkInitializer.razor diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkInitializer.razor b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkInitializer.razor new file mode 100644 index 00000000..9f46c100 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkInitializer.razor @@ -0,0 +1,36 @@ +@using Microsoft.Extensions.Logging +@using Majorsoft.Blazor.Components.Common.JsInterop.Scroll + +@inject IScrollHandler _scrollHandler +@inject NavigationManager _navigationManager +@inject ILogger<IPermaLinkWatcherService> _logger + +@implements IDisposable +@implements IAsyncDisposable + +@code { + private IPermaLinkWatcherService _permalinkWatcher; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + //setup permalink + _permalinkWatcher = new PermaLinkWatcherService(_scrollHandler, _navigationManager, _logger); + _permalinkWatcher.WatchPermaLinks(); + } + } + + public async ValueTask DisposeAsync() + { + if (_scrollHandler is not null) + { + await _scrollHandler.DisposeAsync(); + } + } + + public void Dispose() + { + _permalinkWatcher?.Dispose(); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor b/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor index ba1aaea7..1231fe45 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor +++ b/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor @@ -16,22 +16,13 @@ </div> </div> +@*Permalink initialize*@ +@using Majorsoft.Blazor.Components.PermaLink +<PermaLinkInitializer /> + @* Google Analytics initialize*@ @using Majorsoft.Blazor.Extensions.Analytics.Google <GoogleAnalyticsInitializer TrackingId="G-1QD2VGTEWX" /> -@using Majorsoft.Blazor.Components.PermaLink -@inject IPermaLinkWatcherService _permalinkWatcher -@implements IDisposable - @code { - protected override void OnInitialized() - { - _permalinkWatcher.WatchPermaLinks(); - } - - public void Dispose() - { - _permalinkWatcher.Dispose(); - } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor index 77e894ea..29fdbe2d 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor @@ -16,12 +16,7 @@ @*Permalink initialize*@ @using Majorsoft.Blazor.Components.PermaLink -@using Microsoft.Extensions.Logging -@using Majorsoft.Blazor.Components.Common.JsInterop.Scroll - -@inject IScrollHandler _scrollHandler -@inject NavigationManager _navigationManager -@inject ILogger<IPermaLinkWatcherService> _logger +<PermaLinkInitializer /> @*Server hosted Blazor console log*@ @using Majorsoft.Blazor.Server.Logging.Console @@ -31,30 +26,18 @@ @using Majorsoft.Blazor.Extensions.Analytics.Google <GoogleAnalyticsInitializer TrackingId="G-1QD2VGTEWX" /> -@implements IDisposable @implements IAsyncDisposable @code { - private IPermaLinkWatcherService _permalinkWatcher; - protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - //setup permalink - _permalinkWatcher = new PermaLinkWatcherService(_scrollHandler, _navigationManager, _logger); - _permalinkWatcher.WatchPermaLinks(); - //setup console log await _browserConsoleLogger.StartLoggerAsync(); } } - public void Dispose() - { - _permalinkWatcher?.Dispose(); - } - public async ValueTask DisposeAsync() { if (_browserConsoleLogger is not null) From 83760339201725c75c15523e338210f76fa86c1a Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Tue, 22 Jun 2021 19:51:25 +0200 Subject: [PATCH 39/69] Make demo components works with Sever side Blazor. --- .../Components/BrowserStorage.razor | 5 ++++- .../Components/JsDemo/LangJs.razor | 6 +++++- .../Components/JsDemo/MouseJs.razor | 5 ++++- .../Components/JsDemo/ResizeJs.razor | 5 ++++- .../Components/JsDemo/ScrollJs.razor | 5 ++++- .../Components/MapsGoogle.razor | 5 ++++- 6 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/BrowserStorage.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/BrowserStorage.razor index 867aeb57..98e6e0fa 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/BrowserStorage.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/BrowserStorage.razor @@ -193,8 +193,11 @@ public int Age { get; set; } } - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + //LocalStorage await InsertLocalStorageItems(); _localStorageCount = await _localStorageService.CountAsync(); diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/LangJs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/LangJs.razor index d033841e..c4b64816 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/LangJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/LangJs.razor @@ -20,9 +20,13 @@ @inject ILanguageService _languageService; @code { - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + _detectedBrowserLang = await _languageService.GetBrowserLanguageAsync(); + StateHasChanged(); } //Broswer lang diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/MouseJs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/MouseJs.razor index 886f1992..3684f2d9 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/MouseJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/MouseJs.razor @@ -25,8 +25,11 @@ @inject IGlobalMouseEventHandler _globalMouseEventHandler; @code { - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + await GlobalClickEventHandler(); } diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ResizeJs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ResizeJs.razor index fb3f6fda..3611eb52 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ResizeJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ResizeJs.razor @@ -33,8 +33,11 @@ @code { private string _resizeEventId = null; - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + await ResizeEventHandler(); _pageSize = await _resizeHandler.GetPageSizeAsync(); } 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 432c6cc2..2eaf9dc5 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/ScrollJs.razor @@ -117,8 +117,11 @@ @code { private string _scrollEventId = null; - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + await ScrollEventHandler(); } diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MapsGoogle.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MapsGoogle.razor index 2740e542..1f97aa32 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MapsGoogle.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MapsGoogle.razor @@ -355,8 +355,11 @@ @code { private string _googleMapsApiKey = "AIzaSyAv-6SailPQN1R5PytUAkbdaGI9IHZTU5s"; - protected override void OnInitialized() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + //Static map _staticMapMarkers.ElementAt(0).Locations.Add(new GeolocationData(1.111, 2.222)); _staticMapMarkers.ElementAt(1).Locations.Add(new GeolocationData(17.111, 33.222)); From 222e5d34cf8eb2020f2b5d480fc21bd38a14e460 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Tue, 22 Jun 2021 21:03:55 +0200 Subject: [PATCH 40/69] Renamed Permalink initializers. --- ...azor => PermaLinkBlazorServerInitializer.razor} | 0 .../PermalinkBlazorWasmInitializer.razor | 14 ++++++++++++++ .../Shared/MainLayout.razor | 2 +- .../Shared/MainLayout.razor | 2 +- 4 files changed, 16 insertions(+), 2 deletions(-) rename src/Majorsoft.Blazor.Components.PermaLink/{PermaLinkInitializer.razor => PermaLinkBlazorServerInitializer.razor} (100%) create mode 100644 src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkInitializer.razor b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor similarity index 100% rename from src/Majorsoft.Blazor.Components.PermaLink/PermaLinkInitializer.razor rename to src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor b/src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor new file mode 100644 index 00000000..f59b1e6f --- /dev/null +++ b/src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor @@ -0,0 +1,14 @@ +@inject IPermaLinkWatcherService _permalinkWatcher +@implements IDisposable + +@code { + protected override void OnInitialized() + { + _permalinkWatcher.WatchPermaLinks(); + } + + public void Dispose() + { + _permalinkWatcher.Dispose(); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor b/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor index 1231fe45..fe39508f 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor +++ b/src/Majorsoft.Blazor.Components.TestApp/Shared/MainLayout.razor @@ -18,7 +18,7 @@ @*Permalink initialize*@ @using Majorsoft.Blazor.Components.PermaLink -<PermaLinkInitializer /> +<PermalinkBlazorWasmInitializer /> @* Google Analytics initialize*@ @using Majorsoft.Blazor.Extensions.Analytics.Google diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor index 29fdbe2d..57515469 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Shared/MainLayout.razor @@ -16,7 +16,7 @@ @*Permalink initialize*@ @using Majorsoft.Blazor.Components.PermaLink -<PermaLinkInitializer /> +<PermaLinkBlazorServerInitializer /> @*Server hosted Blazor console log*@ @using Majorsoft.Blazor.Server.Logging.Console From 45f9d447cdeb296e5806a82e8e17a6e9e51b78a6 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Tue, 22 Jun 2021 21:09:31 +0200 Subject: [PATCH 41/69] Permalink initializer docs. --- .github/docs/PermaLink.md | 26 ++++++++++++++++++- .../Components/Permalink.razor | 3 ++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/.github/docs/PermaLink.md b/.github/docs/PermaLink.md index a085b063..1dd3437d 100644 --- a/.github/docs/PermaLink.md +++ b/.github/docs/PermaLink.md @@ -19,8 +19,10 @@ You can try it out by using the [demo app](https://blazorextensions.z6.web.core. - **`PermaLinkElement`**: is a wrapper component it renders the given content with `<a>` tag and will add anchor icon with on hover activated Link copy function. Hover over the top Header item to copy or navigate to URL as well. -- **`IPermaLinkWatcherService`**: . It is registered as Singleton and should be injected only once for the whole application. +- **`IPermaLinkWatcherService`**: is registered as Singleton and should be injected only once for the whole application. Best way to use `MainLayout.razor`. +- **`PermaLinkBlazorServerInitializer`**: (from v1.4.0) convenient wrapper component to initialize navigation watcher in your Blazor Server App `MainLayout.razor` page. +- **`PermalinkBlazorWasmInitializer`**: (from v1.4.0) convenient wrapper component to initialize navigation watcher in your Blazor WebAssembly App `MainLayout.razor` page. ## `IPermaLinkWatcherService` extension This is the main service which makes Permalink navigation possible. **Should be used as a Singleton** only in `MainLayout.razor` file. @@ -125,6 +127,13 @@ Also instance should be disposed. } ``` +**From v1.4.0 a simpler initializer is available!** +``` +@*Permalink initialize*@ +@using Majorsoft.Blazor.Components.PermaLink +<PermalinkBlazorWasmInitializer /> +``` + #### Server hosted projects **In case of Server hosted project register dependency services in your `Startup.cs` file:** @@ -157,6 +166,7 @@ It has to be instantiated manually by using the following code. Also instance sh @inject ILogger<IPermaLinkWatcherService> _logger @implements IDisposable +@implements IAsyncDisposable @code{ private IPermaLinkWatcherService _permalinkWatcher; @@ -170,6 +180,13 @@ It has to be instantiated manually by using the following code. Also instance sh } } + public async ValueTask DisposeAsync() + { + if (_scrollHandler is not null) + { + await _scrollHandler.DisposeAsync(); + } + } public void Dispose() { _permalinkWatcher?.Dispose(); @@ -177,6 +194,13 @@ It has to be instantiated manually by using the following code. Also instance sh } ``` +**From v1.4.0 a simpler initializer is available!** +``` +@*Permalink initialize*@ +@using Majorsoft.Blazor.Components.PermaLink +<PermaLinkBlazorServerInitializer /> +``` + #### Creating permalink (#) navigation points inside a Blazor page This is a standard HTML `a` tag. Apply the well known **`<a name="your-section-name"></a>`** anchor element in your document diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor index 9ed6acb3..32cb67ce 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor @@ -9,7 +9,8 @@ textarea { <p> Blazor <strong>injectable <code>IPermaLinkWatcherService</code> service</strong> and <code>PermaLinkElement</code> wrapper component which allows navigation inside Blazor pages (#permalink). - For usege see soruce code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/PermaLink.md" target="_blank">Github</a>. + To initialize navigation watcher use <code>PermalinkBlazorWasmInitializer</code> or <code>PermaLinkBlazorServerInitializer</code> in your Blazor app <code>MainLayout.razor</code> page. + For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/PermaLink.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.PermaLink</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.PermaLink" target="_blank">Nuget</a> </p> From bba246c17c5a94148d9d3a2d176c32b2f3d569cc Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Thu, 1 Jul 2021 21:28:01 +0200 Subject: [PATCH 42/69] Remove semicolons for @Typeparams --- .../TypeaheadInput.razor | 6 +++--- .../TypeaheadInputText.razor | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Typeahead/TypeaheadInput.razor b/src/Majorsoft.Blazor.Components.Typeahead/TypeaheadInput.razor index 3d19b033..84976eb5 100644 --- a/src/Majorsoft.Blazor.Components.Typeahead/TypeaheadInput.razor +++ b/src/Majorsoft.Blazor.Components.Typeahead/TypeaheadInput.razor @@ -1,7 +1,7 @@ -@typeparam TItem; +@typeparam TItem -@inject ILogger<TypeaheadInput<TItem>> _logger; -@inject IClickBoundariesHandler _clickHandler; +@inject ILogger<TypeaheadInput<TItem>> _logger +@inject IClickBoundariesHandler _clickHandler <DebounceInput class="typeahead" @ref="_typeahead" diff --git a/src/Majorsoft.Blazor.Components.Typeahead/TypeaheadInputText.razor b/src/Majorsoft.Blazor.Components.Typeahead/TypeaheadInputText.razor index 4937bb11..7d6297ff 100644 --- a/src/Majorsoft.Blazor.Components.Typeahead/TypeaheadInputText.razor +++ b/src/Majorsoft.Blazor.Components.Typeahead/TypeaheadInputText.razor @@ -1,7 +1,7 @@ -@typeparam TItem; +@typeparam TItem -@inject ILogger<TypeaheadInputText<TItem>> _logger; -@inject IClickBoundariesHandler _clickHandler; +@inject ILogger<TypeaheadInputText<TItem>> _logger +@inject IClickBoundariesHandler _clickHandler <DebounceInputText class="typeahead" @ref="_typeahead" From 8ba89f8db6775ba7ac8b9dc9b09b59c1d73b11a5 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Thu, 1 Jul 2021 21:50:41 +0200 Subject: [PATCH 43/69] Updated loading on Index page. --- .../wwwroot/index.html | 17 ++++++++++++++++- .../wwwroot/blazor.components.png | Bin 0 -> 8130 bytes .../wwwroot/index.html | 17 ++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.TestApp/wwwroot/blazor.components.png diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/wwwroot/index.html b/demo/Majorsoft.Blazor.Components.DemoApp/wwwroot/index.html index 8da5f191..80b9e60c 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/wwwroot/index.html +++ b/demo/Majorsoft.Blazor.Components.DemoApp/wwwroot/index.html @@ -14,7 +14,22 @@ </head> <body> - <div id="app">Loading...</div> + <div id="app"> + <style> + .center { + text-align: center; + position: absolute; + left: 50%; + top: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + } + </style> + <div class="center"> + <img style="margin: 20px;" src="./blazor.components.png" /> + <h4>Majorsoft Blazor Component <br /> Loading Demo...</h4> + </div> + </div> <div id="blazor-error-ui"> An unhandled error has occurred. diff --git a/src/Majorsoft.Blazor.Components.TestApp/wwwroot/blazor.components.png b/src/Majorsoft.Blazor.Components.TestApp/wwwroot/blazor.components.png new file mode 100644 index 0000000000000000000000000000000000000000..bcb777cea0acfe5cac33f9df5ef685f56917047a GIT binary patch literal 8130 zcmX9@1ymK^*B$!N-AK2TbjPE+mF9scAl+S$6!FpB9fGKYbf-R~>(MCP-SF}I{%g(L zIp^$i_daLtT{E-ReALlW#>Jw-0ssJ4RYgJX`MUbw2|{@uxzDmD0RUN6N5eqz*;OOU z(;zN1<4)8kEQ<n$wt?L|WOzKNCkfQI3G6(_8-bbU@gK0m0~9|S<{YWdInT!P@dR8v z0_Q|PwlGkB0PL$#WzzwL;y~rI`;2}Dem^I$0QsrFuM6M^1`G)S<-9=gv;B+;0~L|L z;Qx3`*;D6$jeFqiD=;erl<@&2&nl0=MLO`um^JM==XqiX&_f7hHv{tz!0EH-2Cy#( zls!YSz`!GL_CKBuV4)jW1_2rWi`@Nxd>r`uj7|gpmVxaSV4fE!83op$K%Y7Lf6Pum z+Y@k331oKwh-dc+IP(EI+kr(<pyD~c3G9gg<)T3O9dH^5^gQ=i4NU$|D;5}jF8ByI z2m^XIfZY{fYYEtF1{MZ@wRT{k1z30jZZCo3Ti`SafIm;k1;#>w-e9070vH$pR-=G{ zbKnFY$TVY5eFM}#H`M_wb^?nyz<*U40zaPb!!2+^0AvyZSsTFKvs@xD>;ZH@fJRxM ziW?|-t}Pk(^%|%P1N!rUF=e3U8Jz<DJ`33aEnYw;29PogteVi~nK9(305xmCP8l%8 z2oz`o-%Ww~XBCnEdc1sY6CFqw1j?Ta=mD0)f&MjM_Zm3e1r8R0ZB5|Y5U|z=%!7ey zW1yi3m^cQGZ2sf#;si=@@B6!-_fVy$o|Zm9jJt&Sh>3)ZMtg$4O~iCV!{R@Dx$$^G z&iNp>oc4GxKq<@&r{c=pI}V)`eMCG$wV4hd`Qq+)cOz|DkG|AuDRsHIE&a)EDl2=i zsovl>)_^G+K91VR$OM{=<$rtVNOF7BvWPSr8jyABc1_$r?Yex7N&4=vA0+<8YUZjb z^whL6U0y5H$uJ*Qw+b#;@ot<98177eJF_^`y6@8mA6tU<tu*GOnJRk$08Nalf}DXb zVm~w5W!;2qAf7wm&?_S9Ap2bmL0UMO3sW1GighB%kD#^hXcoUb8yPqhi8v(764}{H zw2#`4V-K9{^&cCgW^2pEDf#UBT|Tsg+#LFR^}yveska+1Qv5{r{|0jcA`q)5_<QH$ z(Y0ADGIZ?yr}(bDzpg_J{S3b=W*rID4w*4(BMabLJfv7R_bXp5G~aEJBFus}v_fp> zIl7P%Bj!p2buZdBPTC^lJ}<#7vl0sJ3zq+UxG)c?hre+AG4vfg{??j`k(&CgNbR5B zCC146{y$@@Z;Dm79v>n;rtA_bj{nt_64~ODIuCF_Y4$U;PqKfDK7=!iz<_oH!L<C^ z*!d;<1372OvHPv(zuYL>4UyEjd!Wu))OagNAZ8OVP@I3{->0OoQx)0rgxFh@Rrf`x z{ooJbkconZ`6{4;vkJo=SuBquLO!2ZYK&LW;&N9+u~$V(Yh5EAdKkp+K%^+)Z?e0N zQPF)DvAfVPXbV;lA+Hn@szG|CU8i>ozT0Q*=bu7240)Dt=y63#{@_(xX4*QWwvq*X zyR`h*Jz0Pz4<g0sC8&jCvo{RCaO<N*6Gmow^WyUhL9<!5=?@snU41?W!y%fqIj(1@ z-pX2)QH4B^KvQ{Z7!eiY(thN?+TlGaH7K8H+(Da`ulFpye1#11#+?&y6!|vsg;KTP zlbz5vM817V0R*PQ7Wna>HOJT5C!Zj<Teb;j>~ohU=`&&y@6ggLN`>B}Vz;QNJzFNv zNyT-rRDA5-7I}FtP1pgUUO7%NMRBW{Boc=N5h%`vTEzC6c&_Vj#|UB--0wB8VNdRe zZRq?rVpz<(-5U!wp(48{v-k}ZE(%`wjVdg^y`Y|+xsJVWZP&{4>o1oqr8g<FEYZW4 zFHG}FEJIeIM?4akj)NlQF1eN=C+qz;rp*M7R_j<=V<+txB_x!_=G?<$1R(4&%QA_; zE3w)0^&9dy(`FIj^q0}5eW_#1P{CJCE#evWadZmb!*pkzlihDmv6D@MEG;YC)db^G zY}OoTc-E*s6W9UJg?L;ItZ&$6LmDUAyv{4TOqN2OjIsS4?I_H$Ey}MOlBN>S@eIC) zb+j-4&TYA0*9LKN9*dnRwqfLc<9p<=S^t7K{#k(3Bw-YadI`N}@XDp^XzLhb(~*T^ z#iI4=;S=>{k%hz)-J#gvO8zkx_sBGAdMr;Lx&UjSD^R^r6k-WQ`xen6oWWAU5CBmo z;*z_bL#AT<Nq%dCm7u5SW`>M?f}-&Op(8SfN9Qz1xLg1Dn8ApZC+-zw1x2AL%F98M zQvk~ypbS42>zEZh76565YAeXZf?Ac8bE3p05Hw|lK~xsx%XJz^Kml;K{@G~llEc8@ zlSfnq%#@IMHKPyDd*PuA$|@o>#^{bmirXvtE+#Lfcb#bZq7c8L9ok3UAKrsRw@+7k z6#Id`$Y_-tm(tH@GJ3h#8)JJA2zMo5KX_Te$a*ajU0VE8qx0((TbG4gFeSAwRN^=} zt)sJ&R@<V{eOi#(()h-ldM?)U^HOBXS||Y{M>ANO)d;c0)Y8Fke^wdkLHU60>-&PF z|JQt_UwPSJtCMs+yU#at#Mx@A#Md1kCuyU<5-a`ax4XO;5sg-%{{Ju_Y~ZA&K3|Th z7s&Mi3bQA+m=$6+Tee4kHnhKm5ly|oVzxr}o!cZtJgPQK;(DXb35ylg^bwboH6x+H zhy!`(Mllz>`u*BWg03QL!fDwP8G_!iyH`Zz6K$AvdzQL;Z|={lu}E!fK#1$_ok>yZ z<?s=V;kUokc1(rtOWH)tcW#2O@8X!QwMXYOOOm5&*TrE>W$G23bWYhc6M_#uloc<Q zC2Yrc-<H{O^3(FBXELCnuB9W~@I5D6?!@?6oFbPZ)npycIdXUER-Q6W9$c^Qq^$nM zsQQZS-1UsL#+mkgyv4}q!nK%uefZv9)7g4%T6-iyv;BBJQ%#%_r))BHdwwv#Y;NNr z5b(M=uqz<yK0p$QS%ViMBp;dSQ3EvEX}NOat-%q49e&9I@x96x?f#@*#PnPnc!5;o z{qniKPxJWm{WP&SAHHOf9cfJ|;jbN@XZWXbtYO<msN5U$3rZfo7dzWL(;}bxWGNkR zDGi9KQ4#Ogjt4SiSc=12L<IUnH1P>~(9$35u0=r<5H|ECgP*|D_5OTaKFX;K;%j*M zXD3WL@|%4`6LrK3>#*uM1s+h`K07%Lw#1b+O)uISiAp>HneM7E0jI9}OZzte0Cnb* zSUSmb1;6(D29d$Jy}InBi@b)j$}-^YmB0#IOh5u18(F&TWJXBcmb1gyy7P=T2lut0 zpAFt3TGX8_dcuu}&3e~7L+x70?$BLm!AsSz96Z4Q>Ju`f|A`64;UwIWAo$1(_hrMc z-rn}71sNX(Nm*9BC|z3u-j+m)8_qCt5aXwd_0LzKt1HVPUgL;HQj9#}VBQh6VwdX( ztD|E+gF+v@3MQ4zeWl@*xhhoY!;<jp!+vU6g5yg?{FSumEXzEMr$EZrj%;t^a79KB zj;`{gFo+PDG)CVI>-h1oT>Eu#Bi6rMJzf4(PY?F>8Xt#|I^f+(#(mGi5j)yPVP;~w zT|;BFX9l;JM85&)Jt9nNLA~lno&I*ybbGup;lW{i<(&0QwpUjlk#sRu{}3^<C=^ev zvi;!F38bcA<r&eaHSx<ORKdAU!XVo95*4*~+Wi`ovM=<81nsO$o!*Y09&7IG=F1f? z3#eZ#o(D5X6=&Nq_oc(bpOuV-KdQqmyaRAgOq6?66&rYn6D|djhRk_g`drwcf|ZHI zgU68MeR-XT{=w-d-i19usGy8#Y7bdb*Xbbw|6|{YoAvv<j#OLb>wZ6?Fp~kK>-l-v z0CYhEEKT(heQEXics)w2$L0n~h_9=pKzoEy=*<@)%;|F;^cAHY2k$}k{+1JF+7D#s z$&EE6GtE16Fnl~b=TXPe@1j*}XeVP@-YapHHG0eHOf58AoBKp$+0cO7Dk2h+xbOAC z@)XOD?oO($pTt!`7K$&Rt^64R^Ho#{U!wK+x2x3F^b?Rig*XRnULF)LC|iDwq@kk3 zlVoKWD#W8V=CE5+QigD{Uy-t=24B|MLwr5YkKnfvbPglXBOK2zd0f8$LkTRl^Qs@; zz8Lql)aNz_ogWoH(>PT0xYcL{(AZ@|_?@cwyGRGaG!+9DrijyQy{I)nL)FnHdVOBl zjTm*a7Vn_kxlH?>gbEY^1GF}e4@bSd$}jh5;r4FhmpBwxs`+CiXE)Xb!ncsgF;9fw zn1`<l`!Y6zA?gRl$+S=Y=I6aP(9+1z1&}KnIp<E!11<q`Jax0k`S!8v(Xnfw?uhOy z?TJ1W3CcE=$k$f-w>(Jc)i%c5_R1z*5{ET$w1)==pJDNeDGw<q(2$N_JDcerTFSYb z=;`<?-w6w3B?x;)9^xKM^GUxqY{L&yb)JOhSOkwK^Hz|oAIgzIrAtWDhSsqd=2q%X zE2kOvqReXMX8be;qcV<7M?F+<Qz_JwrtktoPSj*{7GJarv5t=2;GHvm-SyB7U(GlE z$;^+7Ud(JWk%fuJNG8h4s!ikNsr%Xy&RY<FK3DK1GP!0ZvE10D6gz=Qqs++JRiK{< z>0OO=T^xm5NVv|u-6E$$4{yO+??-GY)MDI|YQgXtGm>%F{vx-jE~rvXCB4$|qpj3x z>Cb-yk$n7;wV4U+>Ow0zqki4Ky3NoU_813QQz>#~_%(^f+*u_w0hRzeG-Pt%^vo_1 z!C^_((`ly0X_y;3RH{B+*<IPoujeEXBqDwcj+j=SOiKP@%tD&6$??TOl=Fu*j-=;y zCNc`n1-sh>0L}=rnPBbc7bG!=$A9Mu-IzfQk0DKS8_U4`;_k7-DY{cRi1MC{x8{wV zn7p~HHKHWZCHGVvPIYE4!u&Cz$V=2F&@Gma#;VB>QH8A$TV*HsO0b@Y%9Q98qq~Dk zZ9uGk>lChx44MGx1$tImPs*3_>k4{NlKp0D9oZS4W?Zhlms2U-(~Pd*2VC~8GlXRS zS=jB6N)R``b0VXLl0w%8I||8fXRVS(-C<2Tty~DntDY9*21JSF1Mo42S8J2QB5+L* zyD!DOk-ZAirum98?)Ow7Pu0$tnyNX{42T9LSn5jmrKn|6V&YTo?L*|+e245D0Uidr zQfM{TqWA4beIAAvToq}Oa=5Hq^hy!3;5#<AJ?VC<%b#1Az54B)G9Ft!BiZD^0cBTl zJu3R8SOnsMP(Fk{RwuscQjm7F{%%Rs=~W8@a=M^~2|Y>2?ZVlUgdDFR%2g`c-riLZ z+ohT?j-9D27W)`0vt<D&SuPr>RfNmPR6ru|qKA@S;Kx&E5Hdx!AR}eQTQ+~&vN_~m zV8h&aVFGeTjndF+{%*~_7)8F-U#Jst$3v5?ol+jRi#v6FP(E?SXp)0M^wxhAZ%*1@ zr{Tov_m}-Z`V1$+QiK<wyy~K%-KNewpq5r2U`^!uRo~B9L}hGJRnm{@L4({!8Z`B3 zZVZiZPKIR7D0<B=qJv4^6var9^gwmI7o$>L*bfR`K{)Ojpem`L@|bwW-CZp)2*|XV zke#1^ASBu$C12D~cX}NQ<%xcA!=MpS1TS5KMz!gZpjtM@iOd6V12qHIjI<k}F-=`z z-za(vC{kuO8f1NSQNJ`MUbl$IC1+tLODPjVM)*(Stt@up*h@llQzZzqlAq&*)(iY! zMMOUezzJ&H8+fFz`a<`}`tbO@X75szvA*fXz9}N6IC4<r+kDNX?1c0h(|i#H#b5LV zEfzYnyr~Ij)BcKo(1cJBt;7!;0?Thg-dgmk4qxfAPZ%qY`3Rv!a@7+4K8~vD+|5A= zRqTt!jp;f`$OAF|#ySc`ntsd2={`<x*r=P4e%~+}17P!G{mMdqekm+nwGasK8Ba}N z6IFqT746jxiXDfsLsEVuw++qxjUi0)(#c4T!NbSJmyc~Cetn}xcTw%8TRL!OBbl;k zo;1n|v40&wk9;+dfEPEFM%s9&`Y)o6_hQ@aRQaQP5<<i5+xrDF3Y$#kK~Ov%hS2i9 z)Aucm(w&}H3&-QLN3+(dJM~LsjC=^M<z5vQ&$!uE`#p(<+CCOU&1uqK2>;8)QJoe- z5W>81Q8G!2hkH-Q-@409N)ac-qjMuI5ly@+NqEQDxnuiNe_(f$LU!`!1!3tK=}yq` z-qTc3ul$_~srYXOv@y3gt9?j@j=M?CJ4omvUN80J>z$tZ9#vO;6ftB-Ix4M3@p;BX z0^O#+LK#@OU;Px^#a05nY*&_)L%h3MUd+p{*fyz`C4H~ggsn$h+V^5230W*Gi$)&f zW0xsyA6{Lfl+2mo?vVPMw0RPuLdT-JaG--xZ_LPSt;@;nx+X7XdUw@^#WO{PeWq>; z<2rXwpVZQX^;hjhpJTY&S|Z#OcOff{d{Hvc*j?WYghTc2+O3W4M@wB}d;0_A!&@iE zoeR0Lub-v(3gox)@;!W}ZpKsGjV7{zCimqUU(N;{tw|fRGBe>l;w_oGvYZQ=S?{t= zxDD)sKyo~c)|_(1mo@ljG}DV4XLCgjr_oU#Dud@`)GcaN7o>aB!14o$a<A~tL4cCN z3_X}lAXzy&x>#QSZ7?TUjRM%^M(hQVKN8J3n8LmbvUIgL<%>jY&C=ZcIP@nS17lE@ z55<U*iV?+!PsDFdKxmDe0!D~AZQErr@+k}1i<|3oN~P30<J5h8%lA6e=twkqe%aXE z1dChh&NIlpEt+rtu-TTBYNo_|bG&u$unfr_Y@ioKfsJ!Nf!=(nGW!SKeRARi@mbLi zXwKOp*us>`X7*k@$itbQD0+v<Tps|A4nM84v8qkOoj>R3X_#i82&bB#hjK7ujB0IO zU(?sMQ>}>>PR2RBl}_dpxzW9<QE}>2tg)(52X}|ILu5bC1=iTUer5l$eS?{Z03T8a ztNTiLH?p6g>*4Pq9`@ETus-DHlUfY?{#+K7XHBGYEBd$ax+=u(9AWxRee~_GcSZfI z#!p(TZh^-eAIQ_b*mD(D#t#Xapxz8#Y|5Tx20iZ%=exZiIiJc^y;FFO2#;PGcXI6x z4{RDPw|*^Gfo*kjSZe#DwX-uMJbxP<MQeLaxrN*IjsT@G!nSrSeSI0MVdjj`?63}A zrcXj^OQ<2s3OrdvQNdgAa8I>;IFf|)F6CWtB#d!HFKe2P9ofir_`wKNlbbJZ^#pe> z{Le~H!Cf&W6(p+;uF0~qo{Vny8%dFyY(myHG!!r}*QJJ$=fMqwsZxe@IuAJL-9f&g z&G`VcKv`OJI*eH03)ytTj_XK$E5>%-L^nR#VS=Ik`#yB(1<$^EYI`vM5fj1`Oi~T$ z>Z?boBw!Y96KTQ56F=ve2rF;*Z;XSqtk_%P)f+4~iI{#G1rv??iYBYT;gu&St_jF8 zjQT#oQiLk6OL3_Q1dhwpB{xc{t%u#md`|NZ{9X}1d9I6oFS9CsC|{yGz|(~E;r#Wv zYq`3If0}6J$j;YRQ|RA@rJse5)akuq!Tj1x2P2k&Tj84ayBu6{?Iw<eAsSWtP&d*U z9Xs|R;#s3En`g@#=kzovJBu0FAqP0$v4jxy%9FG|0^a)5kX+sqpfI+xJ4mZF#lE9e zT{DtUS!5+bWYN!D(VhS*>>p*9_%?-dF%G($a_$2x!uq(Z@EpDu^5EzbRS_Rne5{0* z(cL`a59*ZI`}(%yIOKCpT^6yT_`L&THbQ;6D@f=)1PGwP=K~Pdhd+}@Q>=Vz6z7>{ zL?2i!g~eUER4CP5adLvgB_Iy@2SV_MVZ3}D4a2lg()uVJJ&<}5pX@|V+V`RKMyHy= zqS=q!Oe5hOS5Jq3Zht0nef*oszT;mzeY#^}qAm^ZUp_fW!ly~$X89pWBG&sCXTv%& zQy0OEfl~PHeLX2f)W49{sza;5!&cauz~B7h+A7NnhbsJs!Tofs*v#-*9@BEaH{X_T zzS9~>Xk!%d6TrBLk?d3C()tr<jzdRlLQD5A#qT=*y~Jh9UdcZ&y7gWE3xi#)sA{Gn zTpC_NGYT;A(miE=E?P{1bG6OVQWG~}EHkF3GV6c(M2?KS)eY#9)sR;3T1W`uA5`v7 z7x#KHI|al>c0}p7-nOzhsI6#I7QhNxHEmnWu-L1ymTynqO^m$D)z%W?O*MV|^RW~L z`wsga&&JAX<RX?B*+ty#6MA@YiRJ0Ad1{*0c{amqd8df^UWji@#oGw_d!=)Bv7H7_ zrx-K2J9#?3u}i<1wcS(ZE>zvgAe76m{W1Ngk?!rOLqLhYWJS*6`YyMoa?d;Z8vm2p z$^dw*sIF5eW3WjC#*(-a=1ua`l<DmsQ_Ba_lPnzobuQlMF@VZgd+nGZw}DUZZCY0{ z5vP#;xtX}f4II>xPM|8{llG!?U-8~JQ-=y;5gt5II^4zN>@1;Ue4UHwE~8}-Fhvx* zEFg$Fh>G-;0Wym4>0}Dqsu5W+*Y|U(c2C-yO$;0$(_5T2#UkJgk(`r{m$<8jwnpEN zPz`fu9?d-7_P`J$2WRa=0zU7k*xKgvkqr<-mlJ>gv<PE<h3cFh`K;8V<<Z@1be(~G z61=zCV^B|~0Cw3j6BQRR3Y|(U=)A`T+&i9bvFx9=nIf5Q@Ri)K2U@j_lW}Z{)YUqp z;^4MULy{DhqUqA*^g$E88M%7O<HgF69+=3tBRjA$REqz%e;+@=wPe1IQ0g~bRZ<37 z>O$pHkM9MZ=4zkT$vq3)`=*cbF4QdMV@s6-<qT!F!aa5M7ZV0*9bHEITe}_@lXQ>n z47Hl-ekrw=^YurqmY@c9yQN#-y-haBM+zBTy!2Ld%05~<iZTE6i_m9iwfQ#av|JnJ zma|xdOa)vQRpBhRH9vpmSkkxk)f%+%Q=w8LZJQ{hu!P($llgxAO5<O@1j5lycX{YA zU32+Ff<`lCcy-u4M=Gt+&ZKMZDvkY<pK)aNF?jGj%c%<KD}K|C=~pZUtTR_GI@!7w z`rV0d_H|89l7-CWCZ{gGZU^c>CFl)kZ-c#}&(kF6zD7g>)TrCbjFEw*dM;*jO_lq2 z3JG~Pc?8;sUlDD}4%&kb4)OjsOIWk)3(022Qayn!M2W8Pq==ICVu-3N>W^(_t`EC! z*ddMH7OLfwnVw}uj8Zvewk<oaan1gH{>PSYLMY>ym((NOIZm_~3(}D-i4qnj<0PCK zKhL@zC)iM*<duJt-$HBpm@=&WI{w?@X|HO)5}L}ZR>4R$B;wE1B@o3S^pUtmSk9s3 zZV<~kk)mWx*zY0PQ(`q0ojyIRIXS$gccp=2Q$N11$x4;`X@qz#%iJajZ4G)6;EL-Q zuc{j2P^6N$aum#!Qw^KV{QD;?rgGfJIYGIc--7?k^(x@s5wkdJ`FleY<Sd#o_z!5z zeck4LiKQ5RYeYfJ(>IdRx)S9aTofp>X5))-BoJ+|{mZgm=P*XeVQ=-aKn_L&qS&&5 z5$f==;)6Qj(OOOKZXPJh+OV<d>dl>VrRSzrBH7z9fGwk(!xA29t_X^?TjFv=PYtaO zp)o;aE0Eg4u=@Fmoe@)VMHhjxhm9hFPDR7<{<D@vnDh`*>F<%ZN2n>twB;;wNeOsq z1v%w`7BXNhc5I3QsN>vR+jkvI<R7ed4LZ2)sGL*yNRCpBfVf>@+sB=xgh6`H#GaFQ zoxHI}ur<gp?i=>DPW+I*60A{<HoTgF?gviYJQG|iOIU3E_f{Y<00(Ni(D7~J>!S=j ztt@6oC&zrl8|`UPZMjnh`u<(q3Q(DBlbrY==RihphPQh4f9(!v5`q4RR)LQzq$)T< zUcq>ifTWUlR3Q2s_|3`I(L8=um1&fU>+lT?s=@=QgMmrMW-rjc(9|q81|DPsU;1jD z+WEXVC3U2*?%e1q;>nd_-7WeZGsgapyU719t5o84Ejj_ryO60+qh$vF=J!c=A#RbI z>i{YS{@s1;zX0r1&1&`~X0rDhnNHwWJ-0ECJeLW$;)mjF5n*b4=0L{Zq(fU>uaRy( z^AT||+Ty`|_vbzs3E+u`Cp=xlc!f<cWyBM{q2*ObZk3Z_A#-;VUM<aCjf0Vq2uQc? zOSCZx`3P*LsOOaLGpp=AfAG~+*}0k^6t9&l3{5b)jv+q0+ur+2>uW`Mp-Y>(8HNlt zF}xQz(apDPV`r?Db$Llb0``B8w_k^r7i2CSsHHTuPC8Rj5ADqnleJ_UCxGR3te)Lg zUVIGv9NN>%7Z9*p&A?d_Je8L^M*f!)Lz-PBLW^*I#jkA3;Yk2lsa>){HM~c`jwBH+ z@6DdSzvf9jGR(vym;j3m=dwc+>0{pO&P&fa9b6JYs#-<bX@gn)zkY&@Y7Q!qn31?2 z%q=^gbN(oQ_CJ-ZeSWH<#qU$C#k^^3Z>YhyHwtny33H<(K>|G{{%L+0AtD{p8T%S( zp+HpDa&|k1^pyf3n(d53b0n3HAn1v`ol1@TNRUsnbL;X$xHUK)7RAZ0z)pr1FT|-C zz1~CqS$C<LGw)x4E~X+n)z;f(te1>O;6hO1ZD5T^@*jfQoal9hFJcdRQTzjeIwf|u wHwc|=J8BZ%9!Dukp@NEWW)ca|lky|BFE6XE++V5L=O3wns-l*{S1<(nfBRNq_y7O^ literal 0 HcmV?d00001 diff --git a/src/Majorsoft.Blazor.Components.TestApp/wwwroot/index.html b/src/Majorsoft.Blazor.Components.TestApp/wwwroot/index.html index 7a7c35b0..1ca74d77 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/wwwroot/index.html +++ b/src/Majorsoft.Blazor.Components.TestApp/wwwroot/index.html @@ -12,7 +12,22 @@ </head> <body> - <app>Loading...</app> + <app> + <style> + .center { + text-align: center; + position: absolute; + left: 50%; + top: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + } + </style> + <div class="center"> + <img style="margin: 20px;" src="./blazor.components.png"/> + <h4>Majorsoft Blazor Component <br /> Loading Demo...</h4> + </div> + </app> <div id="blazor-error-ui"> An unhandled error has occurred. From a389786c386aa685b4aa68c5f780c9a8a09b0f03 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Fri, 2 Jul 2021 18:20:05 +0200 Subject: [PATCH 44/69] Created Majorsoft.Blazor.Components.Inputs component --- .../wwwroot/index.html | 2 +- .../Majorsoft.Blazor.Components.Inputs.csproj | 45 +++++++++++++++++++ .../Majorsoft.Blazor.Components.Inputs.xml | 8 ++++ .../MaxLengthInput.razor | 5 +++ .../_Imports.razor | 1 + ...Majorsoft.Blazor.Components.TestApp.csproj | 1 + .../Pages/MaxLengthInputPage.razor | 3 ++ .../wwwroot/index.html | 2 +- .../Components/MaxLengthInputs.razor | 5 +++ ...t.Blazor.Components.TestApps.Common.csproj | 1 + .../Pages/MaxLengthInputPage.razor | 3 ++ .../Shared/NavMenu.razor | 3 ++ .../Pages/MaxLengthInputPage.razor | 3 ++ src/Majorsoft.Blazor.Components.sln | 7 +++ 14 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.csproj create mode 100644 src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml create mode 100644 src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor create mode 100644 src/Majorsoft.Blazor.Components.Inputs/_Imports.razor create mode 100644 src/Majorsoft.Blazor.Components.TestApp/Pages/MaxLengthInputPage.razor create mode 100644 src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor create mode 100644 src/Majorsoft.Blazor.Components.TestApps.Common/Pages/MaxLengthInputPage.razor create mode 100644 src/Majorsoft.Blazor.Components.TestServerApp/Pages/MaxLengthInputPage.razor diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/wwwroot/index.html b/demo/Majorsoft.Blazor.Components.DemoApp/wwwroot/index.html index 80b9e60c..77a2de42 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/wwwroot/index.html +++ b/demo/Majorsoft.Blazor.Components.DemoApp/wwwroot/index.html @@ -27,7 +27,7 @@ </style> <div class="center"> <img style="margin: 20px;" src="./blazor.components.png" /> - <h4>Majorsoft Blazor Component <br /> Loading Demo...</h4> + <h4>Majorsoft Blazor Components <br /> Loading Demo...</h4> </div> </div> diff --git a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.csproj b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.csproj new file mode 100644 index 00000000..3a7d5657 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.csproj @@ -0,0 +1,45 @@ +<Project Sdk="Microsoft.NET.Sdk.Razor"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + <PackageId>Majorsoft.Blazor.Components.Inputs</PackageId> + <nullable>enable</nullable> + <GeneratePackageOnBuild>true</GeneratePackageOnBuild> + <Version>1.0.0.0</Version> + <Authors>Imre Toth</Authors> + <Company>Majorsoft</Company> + <Product>Blazor Components</Product> + <Copyright>©2021 Imre Toth</Copyright> + <PackageLicenseFile>License.txt</PackageLicenseFile> + <PackageProjectUrl>https://github.com/majorimi/blazor-components/blob/master/.github/docs/Inputs.md</PackageProjectUrl> + <PackageIcon>blazor.components.png</PackageIcon> + <RepositoryUrl>https://github.com/majorimi/blazor-components</RepositoryUrl> + <RepositoryType>Git</RepositoryType> + <PackageReleaseNotes>See Releases here: https://github.com/majorimi/blazor-components/releases</PackageReleaseNotes> + <PackageTags>.Net5 Blazor Htlm.Input Inputs MaxLength Counter</PackageTags> + <Description>Blazor components that renders an Input, InputText, Textarea or InputTextarea, etc. element with maxlength set and counter to show remaining characters. Part of Majorsoft Blazor library.</Description> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <DocumentationFile>.\Majorsoft.Blazor.Components.Inputs.xml</DocumentationFile> + </PropertyGroup> + + <ItemGroup> + <SupportedPlatform Include="browser" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.3" /> + </ItemGroup> + + <ItemGroup> + <None Include="..\..\.github\Images\blazor.components.png"> + <Pack>True</Pack> + <PackagePath></PackagePath> + </None> + <None Include="..\..\.github\License.txt"> + <Pack>True</Pack> + <PackagePath></PackagePath> + </None> + </ItemGroup> +</Project> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml new file mode 100644 index 00000000..21e0a407 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<doc> + <assembly> + <name>Majorsoft.Blazor.Components.Inputs</name> + </assembly> + <members> + </members> +</doc> diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor new file mode 100644 index 00000000..d592bde4 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor @@ -0,0 +1,5 @@ +<input maxlength="10" /> + +@code { + +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Inputs/_Imports.razor b/src/Majorsoft.Blazor.Components.Inputs/_Imports.razor new file mode 100644 index 00000000..77285129 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs/_Imports.razor @@ -0,0 +1 @@ +@using Microsoft.AspNetCore.Components.Web diff --git a/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj b/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj index 99091612..26cd7115 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj +++ b/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj @@ -27,6 +27,7 @@ <Watch Remove="Pages\JSInteropPage.razor" /> <Watch Remove="Pages\LoadingPage.razor" /> <Watch Remove="Pages\MapPage.razor" /> + <Watch Remove="Pages\MaxLengthInputPage.razor" /> <Watch Remove="Pages\ModalPage.razor" /> <Watch Remove="Pages\PermaLinkPage.razor" /> <Watch Remove="Pages\TabsPage.razor" /> diff --git a/src/Majorsoft.Blazor.Components.TestApp/Pages/MaxLengthInputPage.razor b/src/Majorsoft.Blazor.Components.TestApp/Pages/MaxLengthInputPage.razor new file mode 100644 index 00000000..47643464 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApp/Pages/MaxLengthInputPage.razor @@ -0,0 +1,3 @@ +@page "/maxLengthInput" + +<MaxLengthInputs /> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApp/wwwroot/index.html b/src/Majorsoft.Blazor.Components.TestApp/wwwroot/index.html index 1ca74d77..3fa6dc51 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/wwwroot/index.html +++ b/src/Majorsoft.Blazor.Components.TestApp/wwwroot/index.html @@ -25,7 +25,7 @@ </style> <div class="center"> <img style="margin: 20px;" src="./blazor.components.png"/> - <h4>Majorsoft Blazor Component <br /> Loading Demo...</h4> + <h4>Majorsoft Blazor Components <br /> Loading Demo...</h4> </div> </app> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor new file mode 100644 index 00000000..62d4af4d --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor @@ -0,0 +1,5 @@ +<h3>MaxLengthInputs</h3> + +@code { + +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj b/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj index ee5cc31c..f9a7f3a9 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj @@ -29,6 +29,7 @@ <ProjectReference Include="..\Majorsoft.Blazor.Components.Core\Majorsoft.Blazor.Components.Core.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.CssEvents\Majorsoft.Blazor.Components.CssEvents.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Debounce\Majorsoft.Blazor.Components.Debounce.csproj" /> + <ProjectReference Include="..\Majorsoft.Blazor.Components.Inputs\Majorsoft.Blazor.Components.Inputs.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Loading\Majorsoft.Blazor.Components.Loading.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Maps\Majorsoft.Blazor.Components.Maps.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Modal\Majorsoft.Blazor.Components.Modal.csproj" /> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/MaxLengthInputPage.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/MaxLengthInputPage.razor new file mode 100644 index 00000000..47643464 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/MaxLengthInputPage.razor @@ -0,0 +1,3 @@ +@page "/maxLengthInput" + +<MaxLengthInputs /> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor index 729bcfce..f539cc92 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor @@ -42,6 +42,9 @@ <li class="nav-item"> <NavLink class="nav-link" href="typeahead">Typeahead</NavLink> </li> + <li class="nav-item"> + <NavLink class="nav-link" href="maxLengthInput">Max Length</NavLink> + </li> </ul> </div> </li> diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Pages/MaxLengthInputPage.razor b/src/Majorsoft.Blazor.Components.TestServerApp/Pages/MaxLengthInputPage.razor new file mode 100644 index 00000000..47643464 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Pages/MaxLengthInputPage.razor @@ -0,0 +1,3 @@ +@page "/maxLengthInput" + +<MaxLengthInputs /> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.sln b/src/Majorsoft.Blazor.Components.sln index 7f29ea39..b988b268 100644 --- a/src/Majorsoft.Blazor.Components.sln +++ b/src/Majorsoft.Blazor.Components.sln @@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Extensions EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Extensions.Analytics", "Majorsoft.Blazor.Extensions.Analytics\Majorsoft.Blazor.Extensions.Analytics.csproj", "{261B879A-4E63-4D4B-9B59-E8C8F84531FF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Majorsoft.Blazor.Components.Inputs", "Majorsoft.Blazor.Components.Inputs\Majorsoft.Blazor.Components.Inputs.csproj", "{A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -221,6 +223,10 @@ Global {261B879A-4E63-4D4B-9B59-E8C8F84531FF}.Debug|Any CPU.Build.0 = Debug|Any CPU {261B879A-4E63-4D4B-9B59-E8C8F84531FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {261B879A-4E63-4D4B-9B59-E8C8F84531FF}.Release|Any CPU.Build.0 = Release|Any CPU + {A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -257,6 +263,7 @@ Global {CE8AF2E1-6860-4F1B-BC96-F84042E55A2F} = {020CAF9C-D289-470A-BB97-E58D73DDCE70} {B0BDDF05-5508-4D05-B766-6DB53C33D004} = {121A4471-EF7A-4F9A-A856-67E8376A4AD2} {261B879A-4E63-4D4B-9B59-E8C8F84531FF} = {121A4471-EF7A-4F9A-A856-67E8376A4AD2} + {A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC} = {B6905E0B-759A-4928-B38A-9210B5CC8988} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F3E8B7F7-3F7C-4D6C-8B7B-48F1AF4694B9} From 4e0f6eafe8e47f3b62371530af1b77ce394ca658 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Fri, 2 Jul 2021 19:23:23 +0200 Subject: [PATCH 45/69] Implement MaxLengthInput component with demo --- .../Majorsoft.Blazor.Components.Inputs.xml | 41 +++++++++ .../MaxLengthInput.razor | 85 ++++++++++++++++++- .../_Imports.razor | 4 +- .../Components/Debounce.razor | 10 +-- .../Components/MaxLengthInputs.razor | 55 +++++++++++- .../_Imports.razor | 4 +- 6 files changed, 190 insertions(+), 9 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml index 21e0a407..57cd1913 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml +++ b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml @@ -4,5 +4,46 @@ <name>Majorsoft.Blazor.Components.Inputs</name> </assembly> <members> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.InnerElementReference"> + <summary> + Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.Value"> + <summary> + Value of the rendered HTML element. Initial field value can be set to given string or omitted (leave empty). + Also control actual value can be read out (useful when MinLenght not reached). + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.MaxAllowedChars"> + <summary> + Maximum allowed characters to type in. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.CountdownText"> + <summary> + Contdown label text to change or localize message. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.CountdownTextClass"> + <summary> + Contdown label and value CSS calss property to style message. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.OnInput"> + <summary> + Callback function called when HTML control received keyboard inputs. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.OnRemainingCharsChanged"> + <summary> + Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.AllOtherAttributes"> + <summary> + Blazor capture for any unmatched HTML attributes. + </summary> + </member> </members> </doc> diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor index d592bde4..5e5b48b4 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor @@ -1,5 +1,88 @@ -<input maxlength="10" /> +<input @ref="_inputRef" value="@Value" maxlength="@MaxAllowedChars" @oninput="OnTextChange" @attributes=AllOtherAttributes /> +<label class="@CountdownTextClass">@(CountdownText)@(_remainingChars)</label> + +@inject ILogger<MaxLengthInput> _logger; @code { + protected override async Task OnParametersSetAsync() + { + await CalculateRemaining(); + } + + private int _remainingChars; + private ElementReference _inputRef; + /// <summary> + /// Exposes a Blazor <see cref="ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + /// </summary> + public ElementReference InnerElementReference => _inputRef; + + /// <summary> + /// Value of the rendered HTML element. Initial field value can be set to given string or omitted (leave empty). + /// Also control actual value can be read out (useful when MinLenght not reached). + /// </summary> + [Parameter] public string? Value { get; set; } + + /// <summary> + /// Maximum allowed characters to type in. + /// </summary> + [Parameter] public int MaxAllowedChars { get; set; } = 50; + + /// <summary> + /// Contdown label text to change or localize message. + /// </summary> + [Parameter] public string CountdownText { get; set; } = "Remaining characters: "; + + /// <summary> + /// Contdown label and value CSS calss property to style message. + /// </summary> + [Parameter] public string CountdownTextClass { get; set; } + + //Events + /// <summary> + /// Callback function called when HTML control received keyboard inputs. + /// </summary> + [Parameter] public EventCallback<string> OnInput { get; set; } + + /// <summary> + /// Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. + /// </summary> + [Parameter] public EventCallback<int> OnRemainingCharsChanged { get; set; } + + + /// <summary> + /// Blazor capture for any unmatched HTML attributes. + /// </summary> + [Parameter(CaptureUnmatchedValues = true)] + public Dictionary<string, object> AllOtherAttributes { get; set; } + + private async Task OnTextChange(ChangeEventArgs e) + { + WriteDiag($"{nameof(OnTextChange)} event: '{e.Value}', MaxAllowedChars: '{MaxAllowedChars}'."); + + if (OnInput.HasDelegate) //Immediately notify listeners of text change e.g. @bind + { + await OnInput.InvokeAsync(e.Value?.ToString()); + } + + Value = e.Value?.ToString(); + await CalculateRemaining(); + } + + private async Task CalculateRemaining() + { + var tmp = _remainingChars; + _remainingChars = MaxAllowedChars - (Value?.Length ?? 0); + + WriteDiag($"{nameof(CalculateRemaining)} event value Length: '{Value?.Length}', _remainingChars: '{_remainingChars}'."); + + if (OnRemainingCharsChanged.HasDelegate && tmp != _remainingChars) + { + await OnRemainingCharsChanged.InvokeAsync(_remainingChars); + } + } + private void WriteDiag(string message) + { + _logger.LogDebug($"Component {this.GetType()}: {message}"); + } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Inputs/_Imports.razor b/src/Majorsoft.Blazor.Components.Inputs/_Imports.razor index 77285129..848647bc 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.Inputs/_Imports.razor @@ -1 +1,3 @@ -@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.Extensions.Logging; \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Debounce.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Debounce.razor index 911fef99..f8158b1b 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Debounce.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Debounce.razor @@ -2,7 +2,7 @@ <h1>Deboudnce Input controls</h1> <p> - Blazor component that renders an Input, InputText, Textarea or InputTextarea, etc. element with debounced onChange. For usege see soruce code and docs on + Blazor component that renders an Input, InputText, Textarea or InputTextarea, etc. element with debounced onChange. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/DebounceInputs.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Debounce</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Debounce" target="_blank">Nuget</a> </p> @@ -24,7 +24,7 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - <DebounceInput id="in1" class="form-control w-100" placeholder="@("Please type in at least: " + _minCharsLength + " char(s)")" autocomplete="off" + <DebounceInput id="in1" class="form-control w-100" placeholder="@($"Please type in at least: {_minCharsLength} char(s)")" autocomplete="off" @ref="input1" @bind-Value="@_debounceInputValue" @bind-Value:event="OnInput" DebounceTime="@_debounceMilisec" @@ -62,7 +62,7 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> @*<InputText @bind-Value="exampleModel.Name" />*@ - <DebounceInputText class="form-control w-100" placeholder="@("Please type in at least: " + _debounceInputTextMinCharsLength + " char(s)")" + <DebounceInputText class="form-control w-100" placeholder="@($"Please type in at least: {_debounceInputTextMinCharsLength} char(s)")" @ref="inputText1" @bind-Value="exampleModel.Name" DebounceTime="@_debounceInputTextDebounceMilisec" @@ -98,7 +98,7 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - <DebounceTextArea class="form-control w-100" placeholder="@("Please type in at least: " + _debounceTextAreaMinCharsLength + " char(s)")" + <DebounceTextArea class="form-control w-100" placeholder="@($"Please type in at least: {_debounceTextAreaMinCharsLength} char(s)")" @ref="text1" @bind-Value="@_debounceTextAreaText" @bind-Value:event="OnInput" DebounceTime="@_debounceTextAreaMilisec" @@ -135,7 +135,7 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> @*<InputTextArea @bind-Value="exampleModel2.Name" />*@ - <DebounceInputTextArea class="form-control w-100" placeholder="@("Please type in at least: " + _debounceInputTextAreaMinCharsLength + " char(s)")" + <DebounceInputTextArea class="form-control w-100" placeholder="@($"Please type in at least: {_debounceInputTextAreaMinCharsLength} char(s)")" @ref="inputTextArea1" @bind-Value="exampleModel2.Name" DebounceTime="@_debounceInputTextAreaMilisec" diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor index 62d4af4d..d55a9973 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor @@ -1,5 +1,58 @@ -<h3>MaxLengthInputs</h3> +<style> + .countDownText { + font-size: small; + color: gray; + } + .countDownText.red { + color: red; + } +</style> + +<PageScroll /> + +<h1>Max allowed length Input controls with Counter</h1> +<p> + Blazor components that renders an Input, InputText, Textarea or InputTextarea, etc. element with <code>maxlength</code> set and counter to show remaining characters. For usage see source code and docs on + <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Inputs.md" target="_blank">Github</a>. + <br /><strong>Majorsoft.Blazor.Components.Inputs</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Inputs" target="_blank">Nuget</a> +</p> + + +<div class="container-fluid p-3 mb-3 border rounded"> + <h3>MaxLengthInput component</h3> + <p>Wraps around <strong>HTML Input</strong> control and sets <code>maxlength</code> property with notification onChange.</p> + + <div> + <div> + <input type="range" min="0" max="100" @bind="_maxLengthInputAllowed" @oninput="(e => _maxLengthInputAllowed = int.Parse(e.Value?.ToString()))" /> <pre style="display: inline;">Max allowed chars: @_maxLengthInputAllowed</pre> + </div> + </div> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + <MaxLengthInput id="max1" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthInputAllowed} char(s)")" + @bind-Value="@_maxLengthInputValue" @bind-Value:event="OnInput" + MaxAllowedChars="@_maxLengthInputAllowed" + CountdownTextClass="@_maxLengthInputTextClass" + OnRemainingCharsChanged="MaxLengthInputRemainingCharsChanged" /> + </div> + </div> + + <div>Actual value: @_maxLengthInputValue</div> +</div> @code { + private int _maxLengthInputAllowed = 20; + private string _maxLengthInputValue = ""; + private string _maxLengthInputTextClass = "countDownText"; + + private void MaxLengthInputRemainingCharsChanged(int remainingChars) + { + _maxLengthInputTextClass = "countDownText"; + if (_maxLengthInputAllowed * 0.2 >= remainingChars) + { + _maxLengthInputTextClass = "countDownText red"; + } + } } \ 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 3340f34a..dd78556a 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor @@ -49,4 +49,6 @@ @using Majorsoft.Blazor.Extensions.BrowserStorage -@using Majorsoft.Blazor.Extensions.Analytics.Google \ No newline at end of file +@using Majorsoft.Blazor.Extensions.Analytics.Google + +@using Majorsoft.Blazor.Components.Inputs \ No newline at end of file From 921ac075a3f62ea69208302cd9dae61a87d196c8 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Fri, 2 Jul 2021 19:40:45 +0200 Subject: [PATCH 46/69] Implement MaxLengthTextarea with demo. --- .../Majorsoft.Blazor.Components.Inputs.xml | 41 +++++++++ .../MaxLengthTextarea.razor | 88 +++++++++++++++++++ .../Components/MaxLengthInputs.razor | 39 +++++++- 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor diff --git a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml index 57cd1913..2f88adf2 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml +++ b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml @@ -45,5 +45,46 @@ Blazor capture for any unmatched HTML attributes. </summary> </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.InnerElementReference"> + <summary> + Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.Value"> + <summary> + Value of the rendered HTML element. Initial field value can be set to given string or omitted (leave empty). + Also control actual value can be read out (useful when MinLenght not reached). + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.MaxAllowedChars"> + <summary> + Maximum allowed characters to type in. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.CountdownText"> + <summary> + Contdown label text to change or localize message. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.CountdownTextClass"> + <summary> + Contdown label and value CSS calss property to style message. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.OnInput"> + <summary> + Callback function called when HTML control received keyboard inputs. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.OnRemainingCharsChanged"> + <summary> + Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.AllOtherAttributes"> + <summary> + Blazor capture for any unmatched HTML attributes. + </summary> + </member> </members> </doc> diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor new file mode 100644 index 00000000..4f890c70 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor @@ -0,0 +1,88 @@ +<textarea @ref="_inputRef" value="@Value" maxlength="@MaxAllowedChars" @oninput="OnTextChange" @attributes=AllOtherAttributes /> +<label class="@CountdownTextClass">@(CountdownText)@(_remainingChars)</label> + +@inject ILogger<MaxLengthTextarea> _logger; + +@code { + protected override async Task OnParametersSetAsync() + { + await CalculateRemaining(); + } + + private int _remainingChars; + private ElementReference _inputRef; + /// <summary> + /// Exposes a Blazor <see cref="ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + /// </summary> + public ElementReference InnerElementReference => _inputRef; + + /// <summary> + /// Value of the rendered HTML element. Initial field value can be set to given string or omitted (leave empty). + /// Also control actual value can be read out (useful when MinLenght not reached). + /// </summary> + [Parameter] public string? Value { get; set; } + + /// <summary> + /// Maximum allowed characters to type in. + /// </summary> + [Parameter] public int MaxAllowedChars { get; set; } = 50; + + /// <summary> + /// Contdown label text to change or localize message. + /// </summary> + [Parameter] public string CountdownText { get; set; } = "Remaining characters: "; + + /// <summary> + /// Contdown label and value CSS calss property to style message. + /// </summary> + [Parameter] public string CountdownTextClass { get; set; } + + //Events + /// <summary> + /// Callback function called when HTML control received keyboard inputs. + /// </summary> + [Parameter] public EventCallback<string> OnInput { get; set; } + + /// <summary> + /// Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. + /// </summary> + [Parameter] public EventCallback<int> OnRemainingCharsChanged { get; set; } + + + /// <summary> + /// Blazor capture for any unmatched HTML attributes. + /// </summary> + [Parameter(CaptureUnmatchedValues = true)] + public Dictionary<string, object> AllOtherAttributes { get; set; } + + private async Task OnTextChange(ChangeEventArgs e) + { + WriteDiag($"{nameof(OnTextChange)} event: '{e.Value}', MaxAllowedChars: '{MaxAllowedChars}'."); + + if (OnInput.HasDelegate) //Immediately notify listeners of text change e.g. @bind + { + await OnInput.InvokeAsync(e.Value?.ToString()); + } + + Value = e.Value?.ToString(); + await CalculateRemaining(); + } + + private async Task CalculateRemaining() + { + var tmp = _remainingChars; + _remainingChars = MaxAllowedChars - (Value?.Length ?? 0); + + WriteDiag($"{nameof(CalculateRemaining)} event value Length: '{Value?.Length}', _remainingChars: '{_remainingChars}'."); + + if (OnRemainingCharsChanged.HasDelegate && tmp != _remainingChars) + { + await OnRemainingCharsChanged.InvokeAsync(_remainingChars); + } + } + + private void WriteDiag(string message) + { + _logger.LogDebug($"Component {this.GetType()}: {message}"); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor index d55a9973..754b341a 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor @@ -24,7 +24,7 @@ <div> <div> - <input type="range" min="0" max="100" @bind="_maxLengthInputAllowed" @oninput="(e => _maxLengthInputAllowed = int.Parse(e.Value?.ToString()))" /> <pre style="display: inline;">Max allowed chars: @_maxLengthInputAllowed</pre> + <input type="range" min="0" max="50" @bind="_maxLengthInputAllowed" @oninput="(e => _maxLengthInputAllowed = int.Parse(e.Value?.ToString()))" /> <pre style="display: inline;">Max allowed chars: @_maxLengthInputAllowed</pre> </div> </div> @@ -41,6 +41,29 @@ <div>Actual value: @_maxLengthInputValue</div> </div> +<div class="container-fluid p-3 mb-3 border rounded"> + <h3>MaxLengthTextarea component</h3> + <p>Wraps around <strong>HTML TextArea</strong> control and sets <code>maxlength</code> property with notification onChange.</p> + + <div> + <div> + <input type="range" min="0" max="100" @bind="_maxLengthTextareaAllowed" @oninput="(e => _maxLengthTextareaAllowed = int.Parse(e.Value?.ToString()))" /> <pre style="display: inline;">Max allowed chars: @_maxLengthTextareaAllowed</pre> + </div> + </div> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + <MaxLengthTextarea id="max1" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthTextareaAllowed} char(s)")" + @bind-Value="@_maxLengthTextareaValue" @bind-Value:event="OnInput" + MaxAllowedChars="@_maxLengthTextareaAllowed" + CountdownTextClass="@_maxLengthTextareaTextClass" + OnRemainingCharsChanged="MaxLengthTextareaRemainingCharsChanged" /> + </div> + </div> + + <div>Actual value: @_maxLengthTextareaValue</div> +</div> + @code { private int _maxLengthInputAllowed = 20; private string _maxLengthInputValue = ""; @@ -55,4 +78,18 @@ _maxLengthInputTextClass = "countDownText red"; } } + + private int _maxLengthTextareaAllowed = 20; + private string _maxLengthTextareaValue = ""; + private string _maxLengthTextareaTextClass = "countDownText"; + + private void MaxLengthTextareaRemainingCharsChanged(int remainingChars) + { + _maxLengthTextareaTextClass = "countDownText"; + + if (_maxLengthTextareaAllowed * 0.2 >= remainingChars) + { + _maxLengthTextareaTextClass = "countDownText red"; + } + } } \ No newline at end of file From 14a76110a0fbb6bd754f0d36190086fb43c9bff3 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Fri, 2 Jul 2021 19:59:16 +0200 Subject: [PATCH 47/69] Move common code to base class. --- .../Majorsoft.Blazor.Components.Inputs.xml | 56 ++--------- .../MaxLengthInput.razor | 89 +---------------- .../MaxLengthInput.razor.cs | 12 +++ .../MaxLengthInputsBase.cs | 98 +++++++++++++++++++ .../MaxLengthTextarea.razor | 89 +---------------- .../MaxLengthTextarea.razor.cs | 12 +++ .../Components/MaxLengthInputs.razor | 4 +- 7 files changed, 140 insertions(+), 220 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor.cs create mode 100644 src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputsBase.cs create mode 100644 src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor.cs diff --git a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml index 2f88adf2..42c0272b 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml +++ b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml @@ -4,84 +4,48 @@ <name>Majorsoft.Blazor.Components.Inputs</name> </assembly> <members> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.InnerElementReference"> + <member name="T:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase"> <summary> - Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.Value"> - <summary> - Value of the rendered HTML element. Initial field value can be set to given string or omitted (leave empty). - Also control actual value can be read out (useful when MinLenght not reached). - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.MaxAllowedChars"> - <summary> - Maximum allowed characters to type in. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.CountdownText"> - <summary> - Contdown label text to change or localize message. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.CountdownTextClass"> - <summary> - Contdown label and value CSS calss property to style message. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.OnInput"> - <summary> - Callback function called when HTML control received keyboard inputs. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.OnRemainingCharsChanged"> - <summary> - Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. - </summary> - </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInput.AllOtherAttributes"> - <summary> - Blazor capture for any unmatched HTML attributes. + Base class for MaxLength components. </summary> </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.InnerElementReference"> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase.InnerElementReference"> <summary> Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. </summary> </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.Value"> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase.Value"> <summary> Value of the rendered HTML element. Initial field value can be set to given string or omitted (leave empty). Also control actual value can be read out (useful when MinLenght not reached). </summary> </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.MaxAllowedChars"> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase.MaxAllowedChars"> <summary> Maximum allowed characters to type in. </summary> </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.CountdownText"> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase.CountdownText"> <summary> Contdown label text to change or localize message. </summary> </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.CountdownTextClass"> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase.CountdownTextClass"> <summary> Contdown label and value CSS calss property to style message. </summary> </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.OnInput"> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase.OnInput"> <summary> Callback function called when HTML control received keyboard inputs. </summary> </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.OnRemainingCharsChanged"> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase.OnRemainingCharsChanged"> <summary> Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. </summary> </member> - <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthTextarea.AllOtherAttributes"> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase.AllOtherAttributes"> <summary> Blazor capture for any unmatched HTML attributes. </summary> diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor index 5e5b48b4..552520ed 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor @@ -1,88 +1,5 @@ -<input @ref="_inputRef" value="@Value" maxlength="@MaxAllowedChars" @oninput="OnTextChange" @attributes=AllOtherAttributes /> -<label class="@CountdownTextClass">@(CountdownText)@(_remainingChars)</label> - +@inherits MaxLengthInputsBase @inject ILogger<MaxLengthInput> _logger; -@code { - protected override async Task OnParametersSetAsync() - { - await CalculateRemaining(); - } - - private int _remainingChars; - private ElementReference _inputRef; - /// <summary> - /// Exposes a Blazor <see cref="ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. - /// </summary> - public ElementReference InnerElementReference => _inputRef; - - /// <summary> - /// Value of the rendered HTML element. Initial field value can be set to given string or omitted (leave empty). - /// Also control actual value can be read out (useful when MinLenght not reached). - /// </summary> - [Parameter] public string? Value { get; set; } - - /// <summary> - /// Maximum allowed characters to type in. - /// </summary> - [Parameter] public int MaxAllowedChars { get; set; } = 50; - - /// <summary> - /// Contdown label text to change or localize message. - /// </summary> - [Parameter] public string CountdownText { get; set; } = "Remaining characters: "; - - /// <summary> - /// Contdown label and value CSS calss property to style message. - /// </summary> - [Parameter] public string CountdownTextClass { get; set; } - - //Events - /// <summary> - /// Callback function called when HTML control received keyboard inputs. - /// </summary> - [Parameter] public EventCallback<string> OnInput { get; set; } - - /// <summary> - /// Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. - /// </summary> - [Parameter] public EventCallback<int> OnRemainingCharsChanged { get; set; } - - - /// <summary> - /// Blazor capture for any unmatched HTML attributes. - /// </summary> - [Parameter(CaptureUnmatchedValues = true)] - public Dictionary<string, object> AllOtherAttributes { get; set; } - - private async Task OnTextChange(ChangeEventArgs e) - { - WriteDiag($"{nameof(OnTextChange)} event: '{e.Value}', MaxAllowedChars: '{MaxAllowedChars}'."); - - if (OnInput.HasDelegate) //Immediately notify listeners of text change e.g. @bind - { - await OnInput.InvokeAsync(e.Value?.ToString()); - } - - Value = e.Value?.ToString(); - await CalculateRemaining(); - } - - private async Task CalculateRemaining() - { - var tmp = _remainingChars; - _remainingChars = MaxAllowedChars - (Value?.Length ?? 0); - - WriteDiag($"{nameof(CalculateRemaining)} event value Length: '{Value?.Length}', _remainingChars: '{_remainingChars}'."); - - if (OnRemainingCharsChanged.HasDelegate && tmp != _remainingChars) - { - await OnRemainingCharsChanged.InvokeAsync(_remainingChars); - } - } - - private void WriteDiag(string message) - { - _logger.LogDebug($"Component {this.GetType()}: {message}"); - } -} \ No newline at end of file +<input @ref="_inputRef" value="@Value" maxlength="@MaxAllowedChars" @oninput="OnTextChange" @attributes=AllOtherAttributes /> +<label class="@CountdownTextClass">@(CountdownText)@(_remainingChars)</label> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor.cs b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor.cs new file mode 100644 index 00000000..a5058925 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInput.razor.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Logging; + +namespace Majorsoft.Blazor.Components.Inputs +{ + public partial class MaxLengthInput : MaxLengthInputsBase + { + protected override ILogger BaseLogger + { + get { return _logger; } + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputsBase.cs b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputsBase.cs new file mode 100644 index 00000000..c316a8f7 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputsBase.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Logging; + +namespace Majorsoft.Blazor.Components.Inputs +{ + /// <summary> + /// Base class for MaxLength components. + /// </summary> + public abstract class MaxLengthInputsBase : ComponentBase + { + protected abstract ILogger BaseLogger { get; } + + protected override async Task OnParametersSetAsync() + { + await CalculateRemaining(); + } + + protected int _remainingChars; + protected ElementReference _inputRef; + /// <summary> + /// Exposes a Blazor <see cref="ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + /// </summary> + public ElementReference InnerElementReference => _inputRef; + + /// <summary> + /// Value of the rendered HTML element. Initial field value can be set to given string or omitted (leave empty). + /// Also control actual value can be read out (useful when MinLenght not reached). + /// </summary> + [Parameter] public string? Value { get; set; } + + /// <summary> + /// Maximum allowed characters to type in. + /// </summary> + [Parameter] public int MaxAllowedChars { get; set; } = 50; + + /// <summary> + /// Contdown label text to change or localize message. + /// </summary> + [Parameter] public string CountdownText { get; set; } = "Remaining characters: "; + + /// <summary> + /// Contdown label and value CSS calss property to style message. + /// </summary> + [Parameter] public string CountdownTextClass { get; set; } + + //Events + /// <summary> + /// Callback function called when HTML control received keyboard inputs. + /// </summary> + [Parameter] public EventCallback<string> OnInput { get; set; } + + /// <summary> + /// Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. + /// </summary> + [Parameter] public EventCallback<int> OnRemainingCharsChanged { get; set; } + + + /// <summary> + /// Blazor capture for any unmatched HTML attributes. + /// </summary> + [Parameter(CaptureUnmatchedValues = true)] + public Dictionary<string, object> AllOtherAttributes { get; set; } + + protected async Task OnTextChange(ChangeEventArgs e) + { + WriteDiag($"{nameof(OnTextChange)} event: '{e.Value}', MaxAllowedChars: '{MaxAllowedChars}'."); + + if (OnInput.HasDelegate) //Immediately notify listeners of text change e.g. @bind + { + await OnInput.InvokeAsync(e.Value?.ToString()); + } + + Value = e.Value?.ToString(); + await CalculateRemaining(); + } + + private async Task CalculateRemaining() + { + var tmp = _remainingChars; + _remainingChars = MaxAllowedChars - (Value?.Length ?? 0); + + WriteDiag($"{nameof(CalculateRemaining)} event value Length: '{Value?.Length}', _remainingChars: '{_remainingChars}'."); + + if (OnRemainingCharsChanged.HasDelegate && tmp != _remainingChars) + { + await OnRemainingCharsChanged.InvokeAsync(_remainingChars); + } + } + + private void WriteDiag(string message) + { + BaseLogger.LogDebug($"Component {this.GetType()}: {message}"); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor index 4f890c70..d0ba31d6 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor @@ -1,88 +1,5 @@ -<textarea @ref="_inputRef" value="@Value" maxlength="@MaxAllowedChars" @oninput="OnTextChange" @attributes=AllOtherAttributes /> -<label class="@CountdownTextClass">@(CountdownText)@(_remainingChars)</label> - +@inherits MaxLengthInputsBase @inject ILogger<MaxLengthTextarea> _logger; -@code { - protected override async Task OnParametersSetAsync() - { - await CalculateRemaining(); - } - - private int _remainingChars; - private ElementReference _inputRef; - /// <summary> - /// Exposes a Blazor <see cref="ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. - /// </summary> - public ElementReference InnerElementReference => _inputRef; - - /// <summary> - /// Value of the rendered HTML element. Initial field value can be set to given string or omitted (leave empty). - /// Also control actual value can be read out (useful when MinLenght not reached). - /// </summary> - [Parameter] public string? Value { get; set; } - - /// <summary> - /// Maximum allowed characters to type in. - /// </summary> - [Parameter] public int MaxAllowedChars { get; set; } = 50; - - /// <summary> - /// Contdown label text to change or localize message. - /// </summary> - [Parameter] public string CountdownText { get; set; } = "Remaining characters: "; - - /// <summary> - /// Contdown label and value CSS calss property to style message. - /// </summary> - [Parameter] public string CountdownTextClass { get; set; } - - //Events - /// <summary> - /// Callback function called when HTML control received keyboard inputs. - /// </summary> - [Parameter] public EventCallback<string> OnInput { get; set; } - - /// <summary> - /// Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. - /// </summary> - [Parameter] public EventCallback<int> OnRemainingCharsChanged { get; set; } - - - /// <summary> - /// Blazor capture for any unmatched HTML attributes. - /// </summary> - [Parameter(CaptureUnmatchedValues = true)] - public Dictionary<string, object> AllOtherAttributes { get; set; } - - private async Task OnTextChange(ChangeEventArgs e) - { - WriteDiag($"{nameof(OnTextChange)} event: '{e.Value}', MaxAllowedChars: '{MaxAllowedChars}'."); - - if (OnInput.HasDelegate) //Immediately notify listeners of text change e.g. @bind - { - await OnInput.InvokeAsync(e.Value?.ToString()); - } - - Value = e.Value?.ToString(); - await CalculateRemaining(); - } - - private async Task CalculateRemaining() - { - var tmp = _remainingChars; - _remainingChars = MaxAllowedChars - (Value?.Length ?? 0); - - WriteDiag($"{nameof(CalculateRemaining)} event value Length: '{Value?.Length}', _remainingChars: '{_remainingChars}'."); - - if (OnRemainingCharsChanged.HasDelegate && tmp != _remainingChars) - { - await OnRemainingCharsChanged.InvokeAsync(_remainingChars); - } - } - - private void WriteDiag(string message) - { - _logger.LogDebug($"Component {this.GetType()}: {message}"); - } -} \ No newline at end of file +<textarea @ref="_inputRef" value="@Value" maxlength="@MaxAllowedChars" @oninput="OnTextChange" @attributes=AllOtherAttributes /> +<label class="@CountdownTextClass">@(CountdownText)@(_remainingChars)</label> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor.cs b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor.cs new file mode 100644 index 00000000..76a12a8c --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthTextarea.razor.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Logging; + +namespace Majorsoft.Blazor.Components.Inputs +{ + public partial class MaxLengthTextarea : MaxLengthInputsBase + { + protected override ILogger BaseLogger + { + get { return _logger; } + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor index 754b341a..1df7e3b5 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor @@ -30,7 +30,7 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - <MaxLengthInput id="max1" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthInputAllowed} char(s)")" + <MaxLengthInput id="maxInput1" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthInputAllowed} char(s)")" @bind-Value="@_maxLengthInputValue" @bind-Value:event="OnInput" MaxAllowedChars="@_maxLengthInputAllowed" CountdownTextClass="@_maxLengthInputTextClass" @@ -53,7 +53,7 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - <MaxLengthTextarea id="max1" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthTextareaAllowed} char(s)")" + <MaxLengthTextarea id="maxTextArea1" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthTextareaAllowed} char(s)")" @bind-Value="@_maxLengthTextareaValue" @bind-Value:event="OnInput" MaxAllowedChars="@_maxLengthTextareaAllowed" CountdownTextClass="@_maxLengthTextareaTextClass" From c88ad12ddf5cf993994de7d8918b41eda91f2d2c Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Fri, 2 Jul 2021 20:33:26 +0200 Subject: [PATCH 48/69] Fixing max length components and demo --- .../Majorsoft.Blazor.Components.Inputs.xml | 50 ++++++++++++ .../MaxLengthInputText.razor | 75 ++++++++++++++++++ .../MaxLengthInputTextArea.razor | 74 ++++++++++++++++++ .../Components/Collapse.razor | 2 +- .../Components/ColorPicker.razor | 2 +- .../Components/Dialog.razor | 2 +- .../Components/Loading.razor | 2 +- .../Components/Logger.razor | 4 +- .../Components/MaxLengthInputs.razor | 77 +++++++++++++++++-- .../Components/Tabs.razor | 2 +- .../Components/TimerComponent.razor | 2 +- .../Components/Toggle.razor | 2 +- .../Components/Typeahead.razor | 2 +- 13 files changed, 278 insertions(+), 18 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputText.razor create mode 100644 src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputTextArea.razor diff --git a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml index 42c0272b..9a530763 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml +++ b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml @@ -50,5 +50,55 @@ Blazor capture for any unmatched HTML attributes. </summary> </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputText.InnerElementReference"> + <summary> + Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputText.MaxAllowedChars"> + <summary> + Maximum allowed characters to type in. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputText.CountdownText"> + <summary> + Contdown label text to change or localize message. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputText.CountdownTextClass"> + <summary> + Contdown label and value CSS calss property to style message. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputText.OnRemainingCharsChanged"> + <summary> + Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputTextArea.InnerElementReference"> + <summary> + Exposes a Blazor <see cref="T:Microsoft.AspNetCore.Components.ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputTextArea.MaxAllowedChars"> + <summary> + Maximum allowed characters to type in. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputTextArea.CountdownText"> + <summary> + Contdown label text to change or localize message. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputTextArea.CountdownTextClass"> + <summary> + Contdown label and value CSS calss property to style message. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputTextArea.OnRemainingCharsChanged"> + <summary> + Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. + </summary> + </member> </members> </doc> diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputText.razor b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputText.razor new file mode 100644 index 00000000..ea904c67 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputText.razor @@ -0,0 +1,75 @@ +@inherits InputText +@inject ILogger<MaxLengthInputText> _logger; + +<input @ref="_inputRef" value="@CurrentValue" maxlength="@MaxAllowedChars" @oninput="OnTextChange" @attributes=AdditionalAttributes /> +<label class="@CountdownTextClass">@(CountdownText)@(_remainingChars)</label> + +@* + Diffs with DebounceTimerBase: + - value="@CurrentValue" + - remove: [Parameter] public string? Value { get; set; } + - remove: public Dictionary<string, object> AdditionalAttributes { get; set; } + - remove: [Parameter] public EventCallback<string> OnInput { get; set; } + - OnTextChange(): CurrentValue = e.Value?.ToString(); +*@ + +@code { + protected override async Task OnParametersSetAsync() + { + await CalculateRemaining(); + } + + private int _remainingChars; + private ElementReference _inputRef; + /// <summary> + /// Exposes a Blazor <see cref="ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + /// </summary> + public ElementReference InnerElementReference => _inputRef; + + /// <summary> + /// Maximum allowed characters to type in. + /// </summary> + [Parameter] public int MaxAllowedChars { get; set; } = 50; + + /// <summary> + /// Contdown label text to change or localize message. + /// </summary> + [Parameter] public string CountdownText { get; set; } = "Remaining characters: "; + + /// <summary> + /// Contdown label and value CSS calss property to style message. + /// </summary> + [Parameter] public string CountdownTextClass { get; set; } + + + /// <summary> + /// Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. + /// </summary> + [Parameter] public EventCallback<int> OnRemainingCharsChanged { get; set; } + + private async Task OnTextChange(ChangeEventArgs e) + { + WriteDiag($"{nameof(OnTextChange)} event: '{e.Value}', MaxAllowedChars: '{MaxAllowedChars}'."); + + CurrentValue = e.Value?.ToString(); + await CalculateRemaining(); + } + + private async Task CalculateRemaining() + { + var tmp = _remainingChars; + _remainingChars = MaxAllowedChars - (CurrentValue?.Length ?? 0); + + WriteDiag($"{nameof(CalculateRemaining)} event value Length: '{Value?.Length}', _remainingChars: '{_remainingChars}'."); + + if (OnRemainingCharsChanged.HasDelegate && tmp != _remainingChars) + { + await OnRemainingCharsChanged.InvokeAsync(_remainingChars); + } + } + + private void WriteDiag(string message) + { + _logger.LogDebug($"Component {this.GetType()}: {message}"); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputTextArea.razor b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputTextArea.razor new file mode 100644 index 00000000..0dac7c22 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputTextArea.razor @@ -0,0 +1,74 @@ +@inherits InputTextArea +@inject ILogger<MaxLengthInputTextArea> _logger; + +<textarea @ref="_inputRef" value="@CurrentValue" maxlength="@MaxAllowedChars" @oninput="OnTextChange" @attributes=AdditionalAttributes /> +<label class="@CountdownTextClass">@(CountdownText)@(_remainingChars)</label> + +@* + Diffs with DebounceTimerBase: + - value="@CurrentValue" + - remove: [Parameter] public string? Value { get; set; } + - remove: public Dictionary<string, object> AdditionalAttributes { get; set; } + - remove: [Parameter] public EventCallback<string> OnInput { get; set; } + - OnTextChange(): CurrentValue = e.Value?.ToString(); +*@ + +@code { + protected override async Task OnParametersSetAsync() + { + await CalculateRemaining(); + } + + private int _remainingChars; + private ElementReference _inputRef; + /// <summary> + /// Exposes a Blazor <see cref="ElementReference"/> of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + /// </summary> + public ElementReference InnerElementReference => _inputRef; + + /// <summary> + /// Maximum allowed characters to type in. + /// </summary> + [Parameter] public int MaxAllowedChars { get; set; } = 50; + + /// <summary> + /// Contdown label text to change or localize message. + /// </summary> + [Parameter] public string CountdownText { get; set; } = "Remaining characters: "; + + /// <summary> + /// Contdown label and value CSS calss property to style message. + /// </summary> + [Parameter] public string CountdownTextClass { get; set; } + + /// <summary> + /// Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. + /// </summary> + [Parameter] public EventCallback<int> OnRemainingCharsChanged { get; set; } + + private async Task OnTextChange(ChangeEventArgs e) + { + WriteDiag($"{nameof(OnTextChange)} event: '{e.Value}', MaxAllowedChars: '{MaxAllowedChars}'."); + + CurrentValue = e.Value?.ToString(); + await CalculateRemaining(); + } + + private async Task CalculateRemaining() + { + var tmp = _remainingChars; + _remainingChars = MaxAllowedChars - (CurrentValue?.Length ?? 0); + + WriteDiag($"{nameof(CalculateRemaining)} event value Length: '{Value?.Length}', _remainingChars: '{_remainingChars}'."); + + if (OnRemainingCharsChanged.HasDelegate && tmp != _remainingChars) + { + await OnRemainingCharsChanged.InvokeAsync(_remainingChars); + } + } + + private void WriteDiag(string message) + { + _logger.LogDebug($"Component {this.GetType()}: {message}"); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Collapse.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Collapse.razor index 66e7db01..6a36b07e 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Collapse.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Collapse.razor @@ -2,7 +2,7 @@ <h1>Collapse Components</h1> <p> - Blazor component that renders customizable Collapsible/Expandable panel and Accordion with many but only one active panel also custom content and header. For usege see soruce code and docs on + Blazor component that renders customizable Collapsible/Expandable panel and Accordion with many but only one active panel also custom content and header. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Collapse.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Collapse</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Collapse" target="_blank">Nuget</a> </p> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/ColorPicker.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/ColorPicker.razor index da7e53f3..e39b7415 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/ColorPicker.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/ColorPicker.razor @@ -6,7 +6,7 @@ textarea { <h1>Html Color Picker control</h1> <p> - Blazor components that renders a Blazor Color Picker control with color info. For usege see soruce code and docs on + Blazor components that renders a Blazor Color Picker control with color info. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/ColorPicker.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.ColorPicker</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.ColorPicker" target="_blank">Nuget</a> </p> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor index 2f99a9fa..77cfbac2 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor @@ -2,7 +2,7 @@ <h1>Modal Component</h1> <p> - Blazor component that can be used to render Modal dialog window with customizable content and parameterized Overlay, etc.. For usege see soruce code and docs on + Blazor component that can be used to render Modal dialog window with customizable content and parameterized Overlay, etc.. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Modal.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Dialog</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Dialog" target="_blank">Nuget</a> </p> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor index 3151049c..f19796e4 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor @@ -3,7 +3,7 @@ <h1>Loading Components</h1> <p> - Blazor components that renders Overlay for page load. HTML @("<button>") with customizable content for showing async operation in progress/loading state. For usege see soruce code and docs on + Blazor components that renders Overlay for page load. HTML @("<button>") with customizable content for showing async operation in progress/loading state. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Loading.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Loading</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Loading" target="_blank">Nuget</a> </p> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Logger.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Logger.razor index 84376f94..87e142ba 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Logger.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Logger.razor @@ -8,11 +8,11 @@ Blazor Extensions are providing useful features to develop Balazor applications: <ul> <li> - Blazor.Server.Logging.Console: Enables <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/ServerHostedLogging.md" target="_blank">browser conole logging</a> for Blazor applications using <strong>Server Hosted model</strong>. + Blazor.Server.Logging.Console: Enables <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/ServerHostedLogging.md" target="_blank">browser console logging</a> for Blazor applications using <strong>Server Hosted model</strong>. <br /><strong>Majorsoft.Blazor.Server.Logging.Console</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Server.Logging.Console" target="_blank">Nuget</a> </li> <li> - Blazor.WebAssembly.Logging.Console: Enables <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/WebAssemblyHostedLogging.md" target="_blank">browser conole logging</a> for Blazor applications using <strong>WebAssembly Hosting model</strong>. + Blazor.WebAssembly.Logging.Console: Enables <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/WebAssemblyHostedLogging.md" target="_blank">browser console logging</a> for Blazor applications using <strong>WebAssembly Hosting model</strong>. <br /><strong>Majorsoft.Blazor.WebAssembly.Logging.Console</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.WebAssembly.Logging.Console" target="_blank">Nuget</a> </li> </ul> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor index 1df7e3b5..02098ea9 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor @@ -3,9 +3,10 @@ font-size: small; color: gray; } - .countDownText.red { - color: red; - } + + .countDownText.red { + color: red; + } </style> <PageScroll /> @@ -41,6 +42,37 @@ <div>Actual value: @_maxLengthInputValue</div> </div> +<div class="container-fluid p-3 mb-3 border rounded"> + <h3>MaxLengthInputText component</h3> + <p>Wraps around <strong>Blazor InputText</strong> control and sets <code>maxlength</code> property with notification onChange.</p> + + <div> + <div> + <input type="range" min="0" max="50" @bind="_maxLengthInputTextAllowed" @oninput="(e => _maxLengthInputTextAllowed = int.Parse(e.Value?.ToString()))" /> <pre style="display: inline;">Max allowed chars: @_maxLengthInputTextAllowed</pre> + </div> + </div> + + <EditForm Model="@exampleModel"> + <DataAnnotationsValidator /> + <ValidationSummary /> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + <MaxLengthInputText id="maxInput2" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthInputTextAllowed} char(s)")" + @bind-Value="exampleModel.Name" + MaxAllowedChars="@_maxLengthInputTextAllowed" + CountdownTextClass="@_maxLengthInputTextTextClass" + OnRemainingCharsChanged="MaxLengthInputTextRemainingCharsChanged" /> + </div> + </div> + <div class="pb-2"> + <button class="btn btn-primary" type="submit">Submit</button> + </div> + </EditForm> + + <div>Actual value: @(exampleModel.Name)</div> +</div> + + <div class="container-fluid p-3 mb-3 border rounded"> <h3>MaxLengthTextarea component</h3> <p>Wraps around <strong>HTML TextArea</strong> control and sets <code>maxlength</code> property with notification onChange.</p> @@ -54,10 +86,10 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> <MaxLengthTextarea id="maxTextArea1" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthTextareaAllowed} char(s)")" - @bind-Value="@_maxLengthTextareaValue" @bind-Value:event="OnInput" - MaxAllowedChars="@_maxLengthTextareaAllowed" - CountdownTextClass="@_maxLengthTextareaTextClass" - OnRemainingCharsChanged="MaxLengthTextareaRemainingCharsChanged" /> + @bind-Value="@_maxLengthTextareaValue" @bind-Value:event="OnInput" + MaxAllowedChars="@_maxLengthTextareaAllowed" + CountdownTextClass="@_maxLengthTextareaTextClass" + OnRemainingCharsChanged="MaxLengthTextareaRemainingCharsChanged" /> </div> </div> @@ -65,7 +97,8 @@ </div> @code { - private int _maxLengthInputAllowed = 20; + //MaxLengthInput + private int _maxLengthInputAllowed = 10; private string _maxLengthInputValue = ""; private string _maxLengthInputTextClass = "countDownText"; @@ -79,6 +112,22 @@ } } + //MaxLengthInputText + private int _maxLengthInputTextAllowed = 10; + private string _maxLengthInputTextTextClass = "countDownText"; + + private void MaxLengthInputTextRemainingCharsChanged(int remainingChars) + { + _maxLengthInputTextTextClass = "countDownText"; + + if (_maxLengthInputTextAllowed * 0.2 >= remainingChars) + { + _maxLengthInputTextTextClass = "countDownText red"; + } + } + + + //MaxLengthTextarea private int _maxLengthTextareaAllowed = 20; private string _maxLengthTextareaValue = ""; private string _maxLengthTextareaTextClass = "countDownText"; @@ -92,4 +141,16 @@ _maxLengthTextareaTextClass = "countDownText red"; } } + + //MaxLengthInputTextArea + + //Form model + private ExampleModel exampleModel = new ExampleModel() { Name = "" }; + private ExampleModel exampleModel2 = new ExampleModel(); + public class ExampleModel + { + [Required] + [StringLength(10, ErrorMessage = "Name is too long.")] + public string Name { get; set; } + } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor index 4fa8ad56..7cbe7bc6 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor @@ -2,7 +2,7 @@ <h1>Tabs Components</h1> <p> - Blazor component that renders customizable Tabs element panel with many tabs and custom content. For usege see soruce code and docs on + Blazor component that renders customizable Tabs element panel with many tabs and custom content. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Tabs.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Tabs</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Tabs" target="_blank">Nuget</a> </p> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/TimerComponent.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/TimerComponent.razor index 79fcb3be..4b0ea5cf 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/TimerComponent.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/TimerComponent.razor @@ -1,6 +1,6 @@ <h1>Timer Component</h1> <p> - Blazor component that can be used as a simple scheduler or performing periodically repeated tasks by calling custom async code. For usege see soruce code and docs on + Blazor component that can be used as a simple scheduler or performing periodically repeated tasks by calling custom async code. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Timer.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Timer</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Timer" target="_blank">Nuget</a> </p> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Toggle.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Toggle.razor index 1adcd984..e9f11264 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Toggle.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Toggle.razor @@ -2,7 +2,7 @@ <h1>Toggler Components</h1> <p> - Blazor component that renders customizable Toggle Switch and Toggle Button components. For usege see soruce code and docs on + Blazor component that renders customizable Toggle Switch and Toggle Button components. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Toggle.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Toggle</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Toggle" target="_blank">Nuget</a> </p> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Typeahead.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Typeahead.razor index a1b87cfa..24af10fd 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Typeahead.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Typeahead.razor @@ -7,7 +7,7 @@ textarea { <h1>Typeahead Input controls</h1> <p> - Blazor component that renders an HTML Input or InputText with Typeahead panel. For usege see soruce code and docs on + Blazor component that renders an HTML Input or InputText with Typeahead panel. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Typeahead.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Typeahead</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Typeahead" target="_blank">Nuget</a> </p> From 814de94e54736d4c43510cf7c82294b70f908e7c Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Fri, 2 Jul 2021 21:33:19 +0200 Subject: [PATCH 49/69] Finish demo and added link to Index page for Max allowed length Input --- .../Components/Index.razor | 1 + .../Components/MaxLengthInputs.razor | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor index 3aaeb98c..5aee5d9b 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor @@ -32,6 +32,7 @@ <ul> <li><NavLink href="debounceinput">Debounce Inputs</NavLink></li> <li><NavLink href="typeahead">Typeahead Inputs</NavLink></li> + <li><NavLink href="maxLengthInput">Max allowed length with Counter Inputs</NavLink></li> <li><NavLink href="timer">Timer Component</NavLink></li> <li><NavLink href="loading">Loading Components</NavLink></li> <li> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor index 02098ea9..affb6a8e 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor @@ -96,6 +96,36 @@ <div>Actual value: @_maxLengthTextareaValue</div> </div> +<div class="container-fluid p-3 mb-3 border rounded"> + <h3>MaxLengthInputTextArea component</h3> + <p>Wraps around <strong>Blazor InputTextArea</strong> control and sets <code>maxlength</code> property with notification onChange.</p> + + <div> + <div> + <input type="range" min="0" max="50" @bind="_maxLengthInputTextAreaAllowed" @oninput="(e => _maxLengthInputTextAllowed = int.Parse(e.Value?.ToString()))" /> <pre style="display: inline;">Max allowed chars: @_maxLengthInputTextAreaAllowed</pre> + </div> + </div> + + <EditForm Model="@exampleModel2"> + <DataAnnotationsValidator /> + <ValidationSummary /> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + <MaxLengthInputTextArea id="maxTextArea2" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthInputTextAreaAllowed} char(s)")" + @bind-Value="exampleModel2.Name" + MaxAllowedChars="@_maxLengthInputTextAreaAllowed" + CountdownTextClass="@_maxLengthInputTextAreaClass" + OnRemainingCharsChanged="MaxLengthInputTextAreaRemainingCharsChanged" /> + </div> + </div> + <div class="pb-2"> + <button class="btn btn-primary" type="submit">Submit</button> + </div> + </EditForm> + + <div>Actual value: @(exampleModel2.Name)</div> +</div> + @code { //MaxLengthInput private int _maxLengthInputAllowed = 10; @@ -143,6 +173,18 @@ } //MaxLengthInputTextArea + private int _maxLengthInputTextAreaAllowed = 20; + private string _maxLengthInputTextAreaClass = "countDownText"; + + private void MaxLengthInputTextAreaRemainingCharsChanged(int remainingChars) + { + _maxLengthInputTextAreaClass = "countDownText"; + + if (_maxLengthInputTextAreaAllowed * 0.2 >= remainingChars) + { + _maxLengthInputTextAreaClass = "countDownText red"; + } + } //Form model private ExampleModel exampleModel = new ExampleModel() { Name = "" }; From 872b6d2f5415a4f28a27f56b72a7bedc257a71b7 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Fri, 2 Jul 2021 22:20:54 +0200 Subject: [PATCH 50/69] Max allowed length Input docs --- .github/docs/Inputs.md | 202 ++++++++++++++++++ README.md | 3 +- .../Majorsoft.Blazor.Components.Inputs.xml | 12 +- .../MaxLengthInputText.razor | 4 +- .../MaxLengthInputTextArea.razor | 4 +- .../MaxLengthInputsBase.cs | 4 +- .../Components/MaxLengthInputs.razor | 9 +- 7 files changed, 220 insertions(+), 18 deletions(-) create mode 100644 .github/docs/Inputs.md diff --git a/.github/docs/Inputs.md b/.github/docs/Inputs.md new file mode 100644 index 00000000..dcc4b2e0 --- /dev/null +++ b/.github/docs/Inputs.md @@ -0,0 +1,202 @@ +Blazor Components Inputs controls +============ +[![Build Status](https://dev.azure.com/major-soft/GitHub/_apis/build/status/blazor-components/blazor-components-build-check)](https://dev.azure.com/major-soft/GitHub/_build/latest?definitionId=6) +[![Package Version](https://img.shields.io/nuget/v/Majorsoft.Blazor.Components.Inputs?label=Latest%20Version)](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Inputs/) +[![NuGet Downloads](https://img.shields.io/nuget/dt/Majorsoft.Blazor.Components.Inputs?label=Downloads)](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Inputs/) +[![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/majorimi/blazor-components/blob/master/LICENSE) + +# About + +Blazor components that renders an HTML `<input>`, `<textarea>` elements also extending `InputText` and `InputTextArea` Blazor provided components with `maxlength` set and counter to show remaining characters. +**All components work with WebAssembly and Server hosted models**. +For code examples [see usage](https://github.com/majorimi/blazor-components/blob/master/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputPage.razor). + +You can try it out by using the [demo app](https://blazorextensions.z6.web.core.windows.net/maxLengthInput). + +![Debounce demo](https://github.com/majorimi/blazor-components-docs/raw/main/github/docs/gifs/maxLengthInput.gif) + +# Components + +- **`MaxLengthInput`**: wraps and renders HTML `<input>` field and sets `maxlength` property with notification onChange. +- **`MaxLengthInputText`**: extends `InputText` Blazor provided component (it supports form validation and `@bind-Value=`) and sets `maxlength` property with notification onChange. +- **`MaxLengthTextarea`**: wraps and renders HTML `<textarea>` field and sets `maxlength` property with notification onChange. +- **`MaxLengthInputTextArea`**: extends `InputTextArea` Blazor provided component (it supports form validation and `@bind-Value=`) and sets `maxlength` property with notification onChange. + +## `MaxLengthInput` and `MaxLengthInputText` components + +Blazor components that are wrapping around standard HTML `<input>`, `<textarea>` elements and sets `maxlength` property with notification onChange. +Can fit for any Blazor App e.g. when DB field has limited length and on UI same limited chars should be set. And on each chars entered the remaining allowed chars counter will be shown. + +## `MaxLengthTextarea` and `MaxLengthInputTextArea` components + +Blazor components that are wrapping around standard exiting Blazor components. Rendered HTML result will be standard `<input>`, `<textarea>` as well. +Can fit for any Blazor App but use this only when you need Blazor provided FROM validation as well. + +### Properties + +All components have exactly the same features only rendering different HTML elements, hence +they have the same **properties**, **events** and **functions** as well. + +- **`Value`: `string? { get; set; }`** <br /> + Value of the rendered HTML element. Initial field value can be set to given string or omitted (leave empty). + Also control actual value can be read out. + <br />**\* Note**: in case of `DebounceInputText` and `DebounceInputTextArea` this property is inherited from Blazor + component and requires slightly different code with `@bind`, see usage example differences. +- **`MaxAllowedChars`: `int { get; set; }` (default: 50)** <br /> + Maximum allowed characters to type in. +- **`CountdownText`: `string { get; set; }` (default: "Remaining characters: ")** <br /> + Countdown label text to change or localize message. +- **`CountdownTextClass`: `string { get; set; }` (default: "")** <br /> + Countdown label and value CSS class property to style message. +- **`InnerElementReference`: `ElementReference { get; }`** <br /> + Exposes a Blazor `ElementReference` of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + +**Arbitrary HTML attributes e.g.: `id="input1" class="form-control w-25"` will be passed to the corresponding rendered HTML element `<input>` or `<textarea>`**. + +``` +<MaxLengthInput + id="in1" + class="form-control w-25" + placeholder="@($"You can type maximum: {_maxLengthInputTextAreaAllowed} char(s)")" + ... /> +``` + +**Will be rendered to:** +``` +<input id="in1" class="form-control w-25" placeholder="You can type maximum: 20 char(s)" ... /> +``` + +### Events +- **`OnValueChanged`: `EventCallback<string>` delegate** <br /> + Callback function called when value was changed. +- **`OnRemainingCharsChanged`: `EventCallback<int>` delegate** <br /> + Callback function called when HTML control received keyboard inputs remaining allowed chars calculated and sent as even args. + Can be used to style or change the Countdown message. + +# Configuration + +## Installation + +**Majorsoft.Blazor.Components.Inputs** is available on [NuGet](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Inputs/). + +```sh +dotnet add package Majorsoft.Blazor.Components.Inputs +``` +Use the `--version` option to specify a [preview version](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Inputs/absoluteLatest) to install. + +## Usage + +Add using statement to your Blazor <component/page>.razor file. Or globally reference it into `_Imports.razor` file. +``` +@using Majorsoft.Blazor.Components.Inputs +``` + +### `MaxLengthInput` and `MaxLengthTextarea` usage + +Following code example shows how to use **`MaxLengthInput`** component in your Blazor App with model binding +on specific `OnInput` event. So it will enable two way binding between components. You can omit it and use `Value` directly for one way binding. + +**Note**: using **`MaxLengthTextarea`** component basically the same but it will render HTML `<textarea>`. + +``` +<style> + .countDownText { + font-size: small; + color: gray; + } + .countDownText.red { + color: red; + } +</style> + +<div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + <MaxLengthInput id="maxInput1" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthInputAllowed} char(s)")" + @bind-Value="@_maxLengthInputValue" @bind-Value:event="OnInput" + MaxAllowedChars="@_maxLengthInputAllowed" + CountdownTextClass="@_maxLengthInputTextClass" + OnRemainingCharsChanged="MaxLengthInputRemainingCharsChanged" /> + </div> +</div> + +<div>Actual value: @_maxLengthInputValue</div> + +@code { + //MaxLengthInput + private int _maxLengthInputAllowed = 10; + private string _maxLengthInputValue = ""; + private string _maxLengthInputTextClass = "countDownText"; + + private void MaxLengthInputRemainingCharsChanged(int remainingChars) + { + _maxLengthInputTextClass = "countDownText"; + + if (_maxLengthInputAllowed * 0.2 >= remainingChars) + { + _maxLengthInputTextClass = "countDownText red"; + } + } +} +``` + +### `MaxLengthInputText` and `MaxLengthInputTextArea` usage + +Following code example shows how to use **`DebounceInputText`** component with model binding and form validation in your Blazor App. + +**Note**: using **`MaxLengthInputTextArea`** component basically the same but it will render HTML `<textarea>`. + +``` +<style> + .countDownText { + font-size: small; + color: gray; + } + .countDownText.red { + color: red; + } +</style> + +<EditForm Model="@exampleModel"> + <DataAnnotationsValidator /> + <ValidationSummary /> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + <MaxLengthInputText id="maxInput2" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthInputTextAllowed} char(s)")" + @bind-Value="exampleModel.Name" + MaxAllowedChars="@_maxLengthInputTextAllowed" + CountdownTextClass="@_maxLengthInputTextTextClass" + OnRemainingCharsChanged="MaxLengthInputTextRemainingCharsChanged" /> + </div> + </div> + <div class="pb-2"> + <button class="btn btn-primary" type="submit">Submit</button> + </div> +</EditForm> + +<div>Actual value: @(exampleModel.Name)</div> + +@code { + //MaxLengthInputText + private int _maxLengthInputTextAllowed = 10; + private string _maxLengthInputTextTextClass = "countDownText"; + + private void MaxLengthInputTextRemainingCharsChanged(int remainingChars) + { + _maxLengthInputTextTextClass = "countDownText"; + + if (_maxLengthInputTextAllowed * 0.2 >= remainingChars) + { + _maxLengthInputTextTextClass = "countDownText red"; + } + } + + //Form model + private ExampleModel exampleModel = new ExampleModel() { Name = "" }; + public class ExampleModel + { + [Required] + [StringLength(10, ErrorMessage = "Name is too long.")] + public string Name { get; set; } + } +} +``` \ No newline at end of file diff --git a/README.md b/README.md index 111d3c53..bc251d0e 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,9 @@ Check out our planned components and extensions on the project [Wiki page](https **Majorsoft Blazor Components are providing custom UI components to develop Balazor applications:** - **Majorsoft.Blazor.Components.Common.JsInterop**: [Js Interop components, injectable services and extensions](https://github.com/majorimi/blazor-components/blob/master/.github/docs/JsInterop.md) that provides useful functionality and event notifications which can be achieved only with JS Interop e.g. scroll, clipboard, focus, resize, language detection, Geolocation, HTML Head (title, meta, SEO), etc.. -- **Majorsoft.Blazor.Components.Debounce**: [Debounce components](https://github.com/majorimi/blazor-components/blob/master/.github/docs/DebounceInputs.md) that renders an Input, Textarea or other element with debounced onChange. +- **Majorsoft.Blazor.Components.Debounce**: [Debounce components](https://github.com/majorimi/blazor-components/blob/master/.github/docs/DebounceInputs.md) that renders an Input, InputText, Textarea or InputTextarea, etc. element with debounced `onChange` event. - **Majorsoft.Blazor.Components.Typeahead**: [Typeahead components](https://github.com/majorimi/blazor-components/blob/master/.github/docs/Typeahead.md) that renders an HTML Input or InputText with Typeahead panel. +- **Majorsoft.Blazor.Components.Inputs**: [Inputs components](https://github.com/majorimi/blazor-components/blob/master/.github/docs/Inputs.md) renders an Input, InputText, Textarea or InputTextarea, etc. element with `maxlength` set and counter to show remaining characters. - **Majorsoft.Blazor.Components.Loading**: [Loading components](https://github.com/majorimi/blazor-components/blob/master/.github/docs/Loading.md) that renders Overlay for page load. HTML Button with customizable content for showing async operation in progress/loading... - **Majorsoft.Blazor.Components.Timer**: [Timer component](https://github.com/majorimi/blazor-components/blob/master/.github/docs/Timer.md) that can be used for scheduled and periodically repeated tasks to call custom code. - **Majorsoft.Blazor.Components.CssEvents**: [CSS Transition and Animation events](https://github.com/majorimi/blazor-components/blob/master/.github/docs/CssEvents.md) injectable Services and wrapper Components to notify on CSS Transition and Animation events. diff --git a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml index 9a530763..685323af 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml +++ b/src/Majorsoft.Blazor.Components.Inputs/Majorsoft.Blazor.Components.Inputs.xml @@ -27,12 +27,12 @@ </member> <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase.CountdownText"> <summary> - Contdown label text to change or localize message. + Countdown label text to change or localize message. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase.CountdownTextClass"> <summary> - Contdown label and value CSS calss property to style message. + Countdown label and value CSS class property to style message. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputsBase.OnInput"> @@ -62,12 +62,12 @@ </member> <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputText.CountdownText"> <summary> - Contdown label text to change or localize message. + Countdown label text to change or localize message. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputText.CountdownTextClass"> <summary> - Contdown label and value CSS calss property to style message. + Countdown label and value CSS class property to style message. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputText.OnRemainingCharsChanged"> @@ -87,12 +87,12 @@ </member> <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputTextArea.CountdownText"> <summary> - Contdown label text to change or localize message. + Countdown label text to change or localize message. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputTextArea.CountdownTextClass"> <summary> - Contdown label and value CSS calss property to style message. + Countdown label and value CSS class property to style message. </summary> </member> <member name="P:Majorsoft.Blazor.Components.Inputs.MaxLengthInputTextArea.OnRemainingCharsChanged"> diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputText.razor b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputText.razor index ea904c67..f7e7c7ff 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputText.razor +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputText.razor @@ -32,12 +32,12 @@ [Parameter] public int MaxAllowedChars { get; set; } = 50; /// <summary> - /// Contdown label text to change or localize message. + /// Countdown label text to change or localize message. /// </summary> [Parameter] public string CountdownText { get; set; } = "Remaining characters: "; /// <summary> - /// Contdown label and value CSS calss property to style message. + /// Countdown label and value CSS class property to style message. /// </summary> [Parameter] public string CountdownTextClass { get; set; } diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputTextArea.razor b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputTextArea.razor index 0dac7c22..f50ff980 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputTextArea.razor +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputTextArea.razor @@ -32,12 +32,12 @@ [Parameter] public int MaxAllowedChars { get; set; } = 50; /// <summary> - /// Contdown label text to change or localize message. + /// Countdown label text to change or localize message. /// </summary> [Parameter] public string CountdownText { get; set; } = "Remaining characters: "; /// <summary> - /// Contdown label and value CSS calss property to style message. + /// Countdown label and value CSS class property to style message. /// </summary> [Parameter] public string CountdownTextClass { get; set; } diff --git a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputsBase.cs b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputsBase.cs index c316a8f7..d0c7e23d 100644 --- a/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputsBase.cs +++ b/src/Majorsoft.Blazor.Components.Inputs/MaxLengthInputsBase.cs @@ -37,12 +37,12 @@ protected override async Task OnParametersSetAsync() [Parameter] public int MaxAllowedChars { get; set; } = 50; /// <summary> - /// Contdown label text to change or localize message. + /// Countdown label text to change or localize message. /// </summary> [Parameter] public string CountdownText { get; set; } = "Remaining characters: "; /// <summary> - /// Contdown label and value CSS calss property to style message. + /// Countdown label and value CSS class property to style message. /// </summary> [Parameter] public string CountdownTextClass { get; set; } diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor index affb6a8e..aaaa4811 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/MaxLengthInputs.razor @@ -3,10 +3,9 @@ font-size: small; color: gray; } - - .countDownText.red { - color: red; - } + .countDownText.red { + color: red; + } </style> <PageScroll /> @@ -185,7 +184,7 @@ _maxLengthInputTextAreaClass = "countDownText red"; } } - + //Form model private ExampleModel exampleModel = new ExampleModel() { Name = "" }; private ExampleModel exampleModel2 = new ExampleModel(); From b696921a7f5c6e5b901312794b4cb4c9c710c3e6 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Fri, 2 Jul 2021 23:05:00 +0200 Subject: [PATCH 51/69] MaxLengthTextarea unit tests. --- ...soft.Blazor.Components.Inputs.Tests.csproj | 26 ++++ .../MaxLengthInputTest.cs | 134 ++++++++++++++++++ .../MaxLengthTextareaTest.cs | 134 ++++++++++++++++++ src/Majorsoft.Blazor.Components.sln | 7 + 4 files changed, 301 insertions(+) create mode 100644 src/Majorsoft.Blazor.Components.Inputs.Tests/Majorsoft.Blazor.Components.Inputs.Tests.csproj create mode 100644 src/Majorsoft.Blazor.Components.Inputs.Tests/MaxLengthInputTest.cs create mode 100644 src/Majorsoft.Blazor.Components.Inputs.Tests/MaxLengthTextareaTest.cs diff --git a/src/Majorsoft.Blazor.Components.Inputs.Tests/Majorsoft.Blazor.Components.Inputs.Tests.csproj b/src/Majorsoft.Blazor.Components.Inputs.Tests/Majorsoft.Blazor.Components.Inputs.Tests.csproj new file mode 100644 index 00000000..e0d00426 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs.Tests/Majorsoft.Blazor.Components.Inputs.Tests.csproj @@ -0,0 +1,26 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="bunit" Version="1.1.5" /> + <PackageReference Include="bunit.web" Version="1.1.5" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" /> + <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.4" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.4" /> + <PackageReference Include="coverlet.collector" Version="3.0.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Majorsoft.Blazor.Components.Inputs\Majorsoft.Blazor.Components.Inputs.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/Majorsoft.Blazor.Components.Inputs.Tests/MaxLengthInputTest.cs b/src/Majorsoft.Blazor.Components.Inputs.Tests/MaxLengthInputTest.cs new file mode 100644 index 00000000..d64e9078 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs.Tests/MaxLengthInputTest.cs @@ -0,0 +1,134 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Bunit; + +using Moq; + +namespace Majorsoft.Blazor.Components.Inputs.Tests +{ + [TestClass] + public class MaxLengthInputTest + { + private Bunit.TestContext _testContext; + + [TestInitialize] + public void Init() + { + _testContext = new Bunit.TestContext(); + + var logger = new Mock<ILogger<MaxLengthInput>>(); + + _testContext.Services.Add(new ServiceDescriptor(typeof(ILogger<MaxLengthInput>), logger.Object)); + } + + [TestCleanup] + public void Cleanup() + { + _testContext?.Dispose(); + } + + [TestMethod] + public void MaxLengthInput_should_rendered_correctly_html_attributes() + { + var rendered = _testContext.RenderComponent<MaxLengthInput>( + ("id", "id1"), //HTML attributes + ("class", "form-control w-100") //HTML attributes + ); + + var input = rendered.Find("input"); + var label = rendered.Find("label"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + input.MarkupMatches(@"<input maxlength=""50"" id=""id1"" class=""form-control w-100"" >"); + label.MarkupMatches(@"<label>Remaining characters: 50</label>"); + } + + [TestMethod] + public void MaxLengthInput_should_rendered_initial_value() + { + var rendered = _testContext.RenderComponent<MaxLengthInput>(parameters => parameters + .Add(p => p.Value, "test")); + + var input = rendered.Find("input"); + var label = rendered.Find("label"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + input.MarkupMatches(@"<input value=""test"" maxlength=""50""/>"); + label.MarkupMatches(@"<label>Remaining characters: 46</label>"); + } + + [TestMethod] + public void MaxLengthInput_should_rendered_initial_value_with_countdown_text() + { + var rendered = _testContext.RenderComponent<MaxLengthInput>(parameters => parameters + .Add(p => p.Value, "test") + .Add(p => p.CountdownText, "Remaining chars: ")); + + var input = rendered.Find("input"); + var label = rendered.Find("label"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + input.MarkupMatches(@"<input value=""test"" maxlength=""50""/>"); + label.MarkupMatches(@"<label>Remaining chars: 46</label>"); + } + + [TestMethod] + public void MaxLengthInput_should_rendered_initial_MaxAllowedChars() + { + var rendered = _testContext.RenderComponent<MaxLengthInput>(parameters => parameters + .Add(p => p.MaxAllowedChars, 11)); + + var input = rendered.Find("input"); + var label = rendered.Find("label"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + input.MarkupMatches(@"<input maxlength=""11""/>"); + label.MarkupMatches(@"<label>Remaining characters: 11</label>"); + } + + [TestMethod] + public void MaxLengthInput_should_rendered_initial_CountdownTextClass() + { + var rendered = _testContext.RenderComponent<MaxLengthInput>(parameters => parameters + .Add(p => p.CountdownTextClass, "css1 css2")); + + var input = rendered.Find("input"); + var label = rendered.Find("label"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + input.MarkupMatches(@"<input maxlength=""50""/>"); + label.MarkupMatches(@"<label class=""css1 css2"">Remaining characters: 50</label>"); + } + + [TestMethod] + public void MaxLengthInput_should_rendered_and_event_triggered() + { + string text = string.Empty; + int remaining = 0; + + var rendered = _testContext.RenderComponent<MaxLengthInput>(parameters => parameters + .Add(p => p.OnInput, val => { text = val; }) + .Add(p => p.OnRemainingCharsChanged, val => { remaining = val; })); + + var input = rendered.Find("input"); + var label = rendered.Find("label"); + + input.Input("t"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + Assert.AreEqual("t", text); + Assert.AreEqual(49, remaining); + + input.MarkupMatches(@"<input value=""t"" maxlength=""50""/>"); + label.MarkupMatches(@"<label>Remaining characters: 49</label>"); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Inputs.Tests/MaxLengthTextareaTest.cs b/src/Majorsoft.Blazor.Components.Inputs.Tests/MaxLengthTextareaTest.cs new file mode 100644 index 00000000..b5501699 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Inputs.Tests/MaxLengthTextareaTest.cs @@ -0,0 +1,134 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Bunit; + +using Moq; + +namespace Majorsoft.Blazor.Components.Inputs.Tests +{ + [TestClass] + public class MaxLengthTextareaTest + { + private Bunit.TestContext _testContext; + + [TestInitialize] + public void Init() + { + _testContext = new Bunit.TestContext(); + + var logger = new Mock<ILogger<MaxLengthTextarea>>(); + + _testContext.Services.Add(new ServiceDescriptor(typeof(ILogger<MaxLengthTextarea>), logger.Object)); + } + + [TestCleanup] + public void Cleanup() + { + _testContext?.Dispose(); + } + + [TestMethod] + public void MaxLengthTextarea_should_rendered_correctly_html_attributes() + { + var rendered = _testContext.RenderComponent<MaxLengthTextarea>( + ("id", "id1"), //HTML attributes + ("class", "form-control w-100") //HTML attributes + ); + + var input = rendered.Find("textarea"); + var label = rendered.Find("label"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + input.MarkupMatches(@"<textarea maxlength=""50"" id=""id1"" class=""form-control w-100"" ></textarea>"); + label.MarkupMatches(@"<label>Remaining characters: 50</label>"); + } + + [TestMethod] + public void MaxLengthTextarea_should_rendered_initial_value() + { + var rendered = _testContext.RenderComponent<MaxLengthTextarea>(parameters => parameters + .Add(p => p.Value, "test")); + + var input = rendered.Find("textarea"); + var label = rendered.Find("label"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + input.MarkupMatches(@"<textarea value=""test"" maxlength=""50""/>"); + label.MarkupMatches(@"<label>Remaining characters: 46</label>"); + } + + [TestMethod] + public void MaxLengthTextarea_should_rendered_initial_value_with_countdown_text() + { + var rendered = _testContext.RenderComponent<MaxLengthTextarea>(parameters => parameters + .Add(p => p.Value, "test") + .Add(p => p.CountdownText, "Remaining chars: ")); + + var input = rendered.Find("textarea"); + var label = rendered.Find("label"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + input.MarkupMatches(@"<textarea value=""test"" maxlength=""50""/>"); + label.MarkupMatches(@"<label>Remaining chars: 46</label>"); + } + + [TestMethod] + public void MaxLengthTextarea_should_rendered_initial_MaxAllowedChars() + { + var rendered = _testContext.RenderComponent<MaxLengthTextarea>(parameters => parameters + .Add(p => p.MaxAllowedChars, 11)); + + var input = rendered.Find("textarea"); + var label = rendered.Find("label"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + input.MarkupMatches(@"<textarea maxlength=""11""/>"); + label.MarkupMatches(@"<label>Remaining characters: 11</label>"); + } + + [TestMethod] + public void MaxLengthTextarea_should_rendered_initial_CountdownTextClass() + { + var rendered = _testContext.RenderComponent<MaxLengthTextarea>(parameters => parameters + .Add(p => p.CountdownTextClass, "css1 css2")); + + var input = rendered.Find("textarea"); + var label = rendered.Find("label"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + input.MarkupMatches(@"<textarea maxlength=""50""/>"); + label.MarkupMatches(@"<label class=""css1 css2"">Remaining characters: 50</label>"); + } + + [TestMethod] + public void MaxLengthTextarea_should_rendered_and_event_triggered() + { + string text = string.Empty; + int remaining = 0; + + var rendered = _testContext.RenderComponent<MaxLengthTextarea>(parameters => parameters + .Add(p => p.OnInput, val => { text = val; }) + .Add(p => p.OnRemainingCharsChanged, val => { remaining = val; })); + + var input = rendered.Find("textarea"); + var label = rendered.Find("label"); + + input.Input("t"); + + Assert.IsNotNull(input); + Assert.IsNotNull(label); + Assert.AreEqual("t", text); + Assert.AreEqual(49, remaining); + + input.MarkupMatches(@"<textarea value=""t"" maxlength=""50""/>"); + label.MarkupMatches(@"<label>Remaining characters: 49</label>"); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.sln b/src/Majorsoft.Blazor.Components.sln index b988b268..8d9e651f 100644 --- a/src/Majorsoft.Blazor.Components.sln +++ b/src/Majorsoft.Blazor.Components.sln @@ -89,6 +89,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Extensions EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Majorsoft.Blazor.Components.Inputs", "Majorsoft.Blazor.Components.Inputs\Majorsoft.Blazor.Components.Inputs.csproj", "{A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Majorsoft.Blazor.Components.Inputs.Tests", "Majorsoft.Blazor.Components.Inputs.Tests\Majorsoft.Blazor.Components.Inputs.Tests.csproj", "{5A9DBE07-4666-4DEF-841A-477F2A1A28B9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -227,6 +229,10 @@ Global {A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC}.Debug|Any CPU.Build.0 = Debug|Any CPU {A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU {A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC}.Release|Any CPU.Build.0 = Release|Any CPU + {5A9DBE07-4666-4DEF-841A-477F2A1A28B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A9DBE07-4666-4DEF-841A-477F2A1A28B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A9DBE07-4666-4DEF-841A-477F2A1A28B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A9DBE07-4666-4DEF-841A-477F2A1A28B9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -264,6 +270,7 @@ Global {B0BDDF05-5508-4D05-B766-6DB53C33D004} = {121A4471-EF7A-4F9A-A856-67E8376A4AD2} {261B879A-4E63-4D4B-9B59-E8C8F84531FF} = {121A4471-EF7A-4F9A-A856-67E8376A4AD2} {A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC} = {B6905E0B-759A-4928-B38A-9210B5CC8988} + {5A9DBE07-4666-4DEF-841A-477F2A1A28B9} = {020CAF9C-D289-470A-BB97-E58D73DDCE70} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F3E8B7F7-3F7C-4D6C-8B7B-48F1AF4694B9} From 88b49193c4a24cd56a76bd72a06528830165d447 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sat, 3 Jul 2021 21:52:43 +0200 Subject: [PATCH 52/69] Implemented Browser date JS. --- .github/docs/JsInterop.md | 13 ++++++++++ .../BrowserDate/BrowserDateService.cs | 25 +++++++++++++++++++ .../BrowserDate/IBrowserDateService.cs | 17 +++++++++++++ .../JsInteropExtension.cs | 2 ++ ...oft.Blazor.Components.Common.JsInterop.xml | 21 ++++++++++++++++ .../Components/Index.razor | 2 ++ .../Components/JSInterop.razor | 3 +++ .../Components/JsDemo/BrowserDateJs.razor | 24 ++++++++++++++++++ .../_Imports.razor | 2 ++ src/Majorsoft.Blazor.Components.sln | 4 +-- 10 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserDate/BrowserDateService.cs create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserDate/IBrowserDateService.cs create mode 100644 src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserDateJs.razor diff --git a/.github/docs/JsInterop.md b/.github/docs/JsInterop.md index 80667793..b168266e 100644 --- a/.github/docs/JsInterop.md +++ b/.github/docs/JsInterop.md @@ -29,6 +29,7 @@ You can try it out by using the [demo app](https://blazorextensions.z6.web.core. - **Resize JS**: is an **injectable `IResizeHandler` service** for Window (global) and HTML Elements resize event callback handlers. - **Clipboard JS**: is an **injectable `IClipboardHandler` service** for accessing computer Clipboard from Blazor Application. - **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. - **Geo JS**: is an **injectable `IGeolocationService` service** for detect the device Geolocation (GPS position, speed, heading, etc.). - **Head JS**: is an **injectable `IHtmlHeadService` service** for accessing and setting HTML document `Head tags`. @@ -253,6 +254,14 @@ Returns the given user's Browser language preference as .NET `CultureInfo`. - **`DisposeAsync`: `ValueTask IAsyncDisposable()` interface** <br /> Implements `IAsyncDisposable` interface the injected service should be Disposed. +## Browser Date JS (See: [demo app](https://blazorextensions.z6.web.core.windows.net/jsinterop#date-js)) +**Browser Date JS**: is an **injectable `IBrowserDateService` service** is a simple JS call to `new Date();` to retrieve client machine date and time. + +### Functions +- **`GetBrowserDateTimeAsync`**: **`Task<DateTime> GetBrowserDateTimeAsync()`** <br /> +Returns Date and time from browser client machine. + + ## 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. @@ -324,6 +333,10 @@ Add using statement to your Blazor <component/page>.razor file. Or globally refe @using Majorsoft.Blazor.Components.Common.JsInterop.Geo @*Only if you want to use HTML Head tags*@ @using Majorsoft.Blazor.Components.Common.JsInterop.Head +@*Only if you want to use Browser Date*@ +@using Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate +@*Only if you want to use Browser ColorTheme*@ +@using Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme ``` diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserDate/BrowserDateService.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserDate/BrowserDateService.cs new file mode 100644 index 00000000..66086e50 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserDate/BrowserDateService.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; + +using Microsoft.JSInterop; + +namespace Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate +{ + /// <summary> + /// Implementation of <see cref="IBrowserDateService"/> + /// </summary> + public class BrowserDateService : IBrowserDateService + { + private readonly IJSRuntime _jSRuntime; + + public BrowserDateService(IJSRuntime jSRuntime) + { + _jSRuntime = jSRuntime; + } + + public async Task<DateTime> GetBrowserDateTimeAsync() + { + return await _jSRuntime.InvokeAsync<DateTime>("eval", "(new Date()).toJSON();"); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserDate/IBrowserDateService.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserDate/IBrowserDateService.cs new file mode 100644 index 00000000..68b32df3 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserDate/IBrowserDateService.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; + +namespace Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate +{ + /// <summary> + /// Injectable service to query Browser Date with JS Interops. + /// </summary> + public interface IBrowserDateService + { + /// <summary> + /// Returns Date and time from browser client machine. + /// </summary> + /// <returns>Async Task</returns> + Task<DateTime> GetBrowserDateTimeAsync(); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs index 8970ed0f..e8c15320 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs @@ -1,5 +1,6 @@ using System; +using Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate; using Majorsoft.Blazor.Components.Common.JsInterop.Click; using Majorsoft.Blazor.Components.Common.JsInterop.Clipboard; using Majorsoft.Blazor.Components.Common.JsInterop.Focus; @@ -40,6 +41,7 @@ public static IServiceCollection AddJsInteropExtensions(this IServiceCollection services.AddTransient<ILanguageService, LanguageService>(); services.AddTransient<IGeolocationService, GeolocationService>(); services.AddTransient<IHtmlHeadService, HtmlHeadService>(); + services.AddTransient<IBrowserDateService, BrowserDateService>(); 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 1b0ce47c..61c5e31e 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 @@ -4,6 +4,27 @@ <name>Majorsoft.Blazor.Components.Common.JsInterop</name> </assembly> <members> + <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.BrowserColorThemes"> + <summary> + + </summary> + </member> + <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate.BrowserDateService"> + <summary> + Implementation of <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate.IBrowserDateService"/> + </summary> + </member> + <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate.IBrowserDateService"> + <summary> + Injectable service to query Browser Date with JS Interops. + </summary> + </member> + <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate.IBrowserDateService.GetBrowserDateTimeAsync"> + <summary> + Returns Date and time from browser client machine. + </summary> + <returns>Async Task</returns> + </member> <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.Click.ClickBoundariesEventInfo"> <summary> ClickBoundariesEventInfo event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor index 5aee5d9b..10cf04aa 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor @@ -46,7 +46,9 @@ <li><NavLink href="jsinterop#resize-js">Resize Js</NavLink></li> <li><NavLink href="jsinterop#clipboard-js">Clipboard Js</NavLink></li> <li><NavLink href="jsinterop#lang-js">Language Js</NavLink></li> + <li><NavLink href="jsinterop#date-js">Browser Date Js</NavLink></li> <li><NavLink href="jsinterop#geo-js">Geo Js</NavLink></li> + <li><NavLink href="jsinterop#head-js">Head Js</NavLink></li> </ul> </li> <li> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor index 0434ef8c..839156dc 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor @@ -31,6 +31,7 @@ Smooth scroll: <li><NavLink href="jsinterop#resize-js">Resize Js</NavLink></li> <li><NavLink href="jsinterop#clipboard-js">Clipboard Js</NavLink></li> <li><NavLink href="jsinterop#lang-js">Browser Language Js</NavLink></li> + <li><NavLink href="jsinterop#date-js">Browser Date Js</NavLink></li> <li><NavLink href="jsinterop#geo-js">Geolocation Js</NavLink></li> <li><NavLink href="jsinterop#head-js">Head Js</NavLink></li> </ul> @@ -52,6 +53,8 @@ Smooth scroll: <LangJs /> +<BrowserDateJs /> + <GeoJs /> <HeadJs /> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserDateJs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserDateJs.razor new file mode 100644 index 00000000..e678cce7 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserDateJs.razor @@ -0,0 +1,24 @@ +<div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="date-js" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>Browser Date JS</h3></Content> + </PermaLinkElement> + + <strong>Browser Date JS</strong> is a simple JS call to <code>new Date();</code> to retrieve client machine date and time. + + <AdvancedTimer IsEnabled="true" IntervalInMilisec="200" Occurring="Times.Infinite()" AutoStart="true" OnIntervalElapsed="@Clock" /> + <p> + <span>Browser date time: <strong>@_date</strong></span> + </p> + +</div> + +@inject IBrowserDateService _dateService + +@code { + private System.DateTime _date; + + private async Task Clock() + { + _date = await _dateService.GetBrowserDateTimeAsync(); + } +} \ 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 dd78556a..5ac86437 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor @@ -35,6 +35,8 @@ @using Majorsoft.Blazor.Components.Common.JsInterop.Language @using Majorsoft.Blazor.Components.Common.JsInterop.Geo @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.PermaLink diff --git a/src/Majorsoft.Blazor.Components.sln b/src/Majorsoft.Blazor.Components.sln index 8d9e651f..a8b8ef25 100644 --- a/src/Majorsoft.Blazor.Components.sln +++ b/src/Majorsoft.Blazor.Components.sln @@ -87,9 +87,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Extensions EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Extensions.Analytics", "Majorsoft.Blazor.Extensions.Analytics\Majorsoft.Blazor.Extensions.Analytics.csproj", "{261B879A-4E63-4D4B-9B59-E8C8F84531FF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Majorsoft.Blazor.Components.Inputs", "Majorsoft.Blazor.Components.Inputs\Majorsoft.Blazor.Components.Inputs.csproj", "{A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Components.Inputs", "Majorsoft.Blazor.Components.Inputs\Majorsoft.Blazor.Components.Inputs.csproj", "{A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Majorsoft.Blazor.Components.Inputs.Tests", "Majorsoft.Blazor.Components.Inputs.Tests\Majorsoft.Blazor.Components.Inputs.Tests.csproj", "{5A9DBE07-4666-4DEF-841A-477F2A1A28B9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Components.Inputs.Tests", "Majorsoft.Blazor.Components.Inputs.Tests\Majorsoft.Blazor.Components.Inputs.Tests.csproj", "{5A9DBE07-4666-4DEF-841A-477F2A1A28B9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 4e3e0149c7466a16bbb2aeef2ede259f60f3627a Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sun, 4 Jul 2021 21:27:49 +0200 Subject: [PATCH 53/69] Implemented IBrowserThemeService --- .github/docs/JsInterop.md | 14 ++++ .../BrowserColorTheme/BrowserColorThemes.cs | 11 +++ .../BrowserThemeEventInfo.cs | 28 +++++++ .../BrowserColorTheme/BrowserThemeService.cs | 84 +++++++++++++++++++ .../BrowserColorTheme/IBrowserThemeService.cs | 31 +++++++ .../JsInteropExtension.cs | 2 + ...oft.Blazor.Components.Common.JsInterop.xml | 37 +++++++- .../bundleconfig.json | 6 ++ .../wwwroot/colorTheme.js | 84 +++++++++++++++++++ .../wwwroot/colorTheme.min.js | 1 + .../Components/Index.razor | 1 + .../Components/JSInterop.razor | 3 + .../Components/JsDemo/BrowserDateJs.razor | 2 +- .../Components/JsDemo/BrowserThemeJs.razor | 58 +++++++++++++ 14 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserColorThemes.cs create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserThemeEventInfo.cs create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserThemeService.cs create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/IBrowserThemeService.cs create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/colorTheme.js create mode 100644 src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/colorTheme.min.js create mode 100644 src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserThemeJs.razor diff --git a/.github/docs/JsInterop.md b/.github/docs/JsInterop.md index b168266e..f677a47d 100644 --- a/.github/docs/JsInterop.md +++ b/.github/docs/JsInterop.md @@ -30,6 +30,7 @@ You can try it out by using the [demo app](https://blazorextensions.z6.web.core. - **Clipboard JS**: is an **injectable `IClipboardHandler` service** for accessing computer Clipboard from Blazor Application. - **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.). - **Head JS**: is an **injectable `IHtmlHeadService` service** for accessing and setting HTML document `Head tags`. @@ -261,6 +262,19 @@ Implements `IAsyncDisposable` interface the injected service should be Disposed. - **`GetBrowserDateTimeAsync`**: **`Task<DateTime> GetBrowserDateTimeAsync()`** <br /> Returns Date and time from browser client machine. +## Browser Theme JS (See: [demo app](https://blazorextensions.z6.web.core.windows.net/jsinterop#theme-js)) +**Browser Theme JS** is an injectable `IBrowserThemeService` to handle Browser color scheme queries and changes. + +### Functions +- **`GetBrowserColorThemeAsync`**: **`Task<BrowserColorThemes> GetBrowserColorThemeAsync()`** <br /> +Browser color scheme queries to return actual `prefers-color-scheme`. +- **`RegisterColorThemeChangeAsync`**: **`Task<string> RegisterColorThemeChangeAsync(Func<BrowserColorThemes, Task> colorThemeChangeCallback)`** <br /> +Adds event listener for `prefers-color-scheme` HTML event for the Browser. +- **`RemoveColorThemeChangeAsync`**: **`Task RemoveColorThemeChangeAsync()`** <br /> +Removes event listener for `prefers-color-scheme` HTML event for the Browser. +- **`DisposeAsync`: `ValueTask IAsyncDisposable()` interface** <br /> +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.)**. diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserColorThemes.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserColorThemes.cs new file mode 100644 index 00000000..85bcef5f --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserColorThemes.cs @@ -0,0 +1,11 @@ +namespace Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme +{ + /// <summary> + /// Browsers preferred color scheme/theme + /// </summary> + public enum BrowserColorThemes + { + Dark = 0, + Light = 1 + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserThemeEventInfo.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserThemeEventInfo.cs new file mode 100644 index 00000000..750dbc8c --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserThemeEventInfo.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; + +using Microsoft.JSInterop; + +namespace Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme +{ + /// <summary> + /// Browser color scheme queries response event <see cref="DotNetObjectReference"/> info to handle JS callback + /// </summary> + internal sealed class BrowserThemeEventInfo + { + private readonly Func<BrowserColorThemes, Task> _browserThemeChangedEventCallback; + internal string EventId { get; } + + public BrowserThemeEventInfo(Func<BrowserColorThemes, Task> browserThemeChangedEventCallback, string eventId) + { + _browserThemeChangedEventCallback = browserThemeChangedEventCallback; + EventId = eventId; + } + + [JSInvokable("BrowserThemeChanged")] + public async Task TransitionEvent(int colorTheme) + { + await _browserThemeChangedEventCallback((BrowserColorThemes)colorTheme); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserThemeService.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserThemeService.cs new file mode 100644 index 00000000..4d219ff7 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/BrowserThemeService.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Microsoft.JSInterop; + +namespace Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme +{ + /// <summary> + /// Implementation of <see cref="IBrowserThemeService"/> + /// </summary> + public class BrowserThemeService : IBrowserThemeService + { + private List<DotNetObjectReference<BrowserThemeEventInfo>> _dotNetObjectReferences; + private readonly Lazy<Task<IJSObjectReference>> _moduleTask; + private readonly IJSRuntime _jsRuntime; + + public BrowserThemeService(IJSRuntime jsRuntime) + { + _jsRuntime = jsRuntime; + + string js = ""; +#if DEBUG + js = "./_content/Majorsoft.Blazor.Components.Common.JsInterop/colorTheme.js"; +#else + js = "./_content/Majorsoft.Blazor.Components.Common.JsInterop/colorTheme.min.js"; +#endif + + _moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>("import", js).AsTask()); + _dotNetObjectReferences = new List<DotNetObjectReference<BrowserThemeEventInfo>>(); + } + + public async Task<BrowserColorThemes> GetBrowserColorThemeAsync() + { + var module = await _moduleTask.Value; + var ret = await module.InvokeAsync<int>("getColorTheme"); + + return ret == 0 ? BrowserColorThemes.Dark : BrowserColorThemes.Light; + } + + public async Task<string> RegisterColorThemeChangeAsync(Func<BrowserColorThemes, Task> colorThemeChangeCallback) + { + var module = await _moduleTask.Value; + + var id = Guid.NewGuid().ToString(); + var info = new BrowserThemeEventInfo(colorThemeChangeCallback, id); + var dotnetRef = DotNetObjectReference.Create<BrowserThemeEventInfo>(info); + _dotNetObjectReferences.Add(dotnetRef); + + await module.InvokeVoidAsync("addColorThemeEvent", dotnetRef, id); + return id; + } + + public async Task RemoveColorThemeChangeAsync(string eventId) + { + var module = await _moduleTask.Value; + + await module.InvokeVoidAsync("removeColorThemeEvent", eventId); + RemoveElement(eventId); + } + + private void RemoveElement(string eventId) + { + var dotNetRefs = _dotNetObjectReferences.Where(x => x.Value.EventId == eventId); + _dotNetObjectReferences = _dotNetObjectReferences.Except(dotNetRefs).ToList(); + + foreach (var item in dotNetRefs) + { + item.Dispose(); + } + } + + public async ValueTask DisposeAsync() + { + if (_moduleTask.IsValueCreated) + { + var module = await _moduleTask.Value; + await module.InvokeVoidAsync("dispose", (object)_dotNetObjectReferences.Select(s => s.Value.EventId).ToArray()); + await module.DisposeAsync(); + } + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/IBrowserThemeService.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/IBrowserThemeService.cs new file mode 100644 index 00000000..85b0b0e2 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/BrowserColorTheme/IBrowserThemeService.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; + +namespace Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme +{ + /// <summary> + /// Injectable service to handle Browser color scheme queries and changes. + /// </summary> + public interface IBrowserThemeService : IAsyncDisposable + { + /// <summary> + /// Browser color scheme queries to return actual `prefers-color-scheme`. + /// </summary> + /// <returns>Async Task</returns> + Task<BrowserColorThemes> GetBrowserColorThemeAsync(); + + /// <summary> + /// Adds event listener for `prefers-color-scheme` HTML event for the Browser. + /// </summary> + /// <param name="colorThemeChangeCallback"></param> + /// <returns>Async Task</returns> + Task<string> RegisterColorThemeChangeAsync(Func<BrowserColorThemes, Task> colorThemeChangeCallback); + + /// <summary> + /// Removes event listener for `prefers-color-scheme` HTML event for the Browser. + /// </summary> + /// <param name="eventId">Event id from <see cref="RegisterColorThemeChangeAsync"/></param> + /// <returns>Async Task</returns> + Task RemoveColorThemeChangeAsync(string eventId); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs b/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs index e8c15320..3940404e 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/JsInteropExtension.cs @@ -1,5 +1,6 @@ using System; +using Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme; using Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate; using Majorsoft.Blazor.Components.Common.JsInterop.Click; using Majorsoft.Blazor.Components.Common.JsInterop.Clipboard; @@ -42,6 +43,7 @@ public static IServiceCollection AddJsInteropExtensions(this IServiceCollection services.AddTransient<IGeolocationService, GeolocationService>(); services.AddTransient<IHtmlHeadService, HtmlHeadService>(); services.AddTransient<IBrowserDateService, BrowserDateService>(); + services.AddTransient<IBrowserThemeService, BrowserThemeService>(); 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 61c5e31e..dc12d0cd 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 @@ -6,9 +6,44 @@ <members> <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.BrowserColorThemes"> <summary> - + Browsers preferred color scheme/theme </summary> </member> + <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.BrowserThemeEventInfo"> + <summary> + Browser color scheme queries response event <see cref="T:Microsoft.JSInterop.DotNetObjectReference"/> info to handle JS callback + </summary> + </member> + <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.BrowserThemeService"> + <summary> + Implementation of <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.IBrowserThemeService"/> + </summary> + </member> + <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.IBrowserThemeService"> + <summary> + Injectable service to handle Browser color scheme queries and changes. + </summary> + </member> + <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.IBrowserThemeService.GetBrowserColorThemeAsync"> + <summary> + Browser color scheme queries to return actual `prefers-color-scheme`. + </summary> + <returns>Async Task</returns> + </member> + <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.IBrowserThemeService.RegisterColorThemeChangeAsync(System.Func{Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.BrowserColorThemes,System.Threading.Tasks.Task})"> + <summary> + Adds event listener for `prefers-color-scheme` HTML event for the Browser. + </summary> + <param name="colorThemeChangeCallback"></param> + <returns>Async Task</returns> + </member> + <member name="M:Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.IBrowserThemeService.RemoveColorThemeChangeAsync(System.String)"> + <summary> + Removes event listener for `prefers-color-scheme` HTML event for the Browser. + </summary> + <param name="eventId">Event id from <see cref="M:Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.IBrowserThemeService.RegisterColorThemeChangeAsync(System.Func{Majorsoft.Blazor.Components.Common.JsInterop.BrowserColorTheme.BrowserColorThemes,System.Threading.Tasks.Task})"/></param> + <returns>Async Task</returns> + </member> <member name="T:Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate.BrowserDateService"> <summary> Implementation of <see cref="T:Majorsoft.Blazor.Components.Common.JsInterop.BrowserDate.IBrowserDateService"/> diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json b/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json index bcb81836..ce969c52 100644 --- a/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/bundleconfig.json @@ -70,5 +70,11 @@ "inputFiles": [ "Scroll/ScrollToPageBottom.razor.css" ] + }, + { + "outputFileName": "wwwroot/colorTheme.min.js", + "inputFiles": [ + "wwwroot/colorTheme.js" + ] } ] diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/colorTheme.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/colorTheme.js new file mode 100644 index 00000000..1c796773 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/colorTheme.js @@ -0,0 +1,84 @@ +export function getColorTheme() { + return getTheme(); +} +export function addColorThemeEvent(dotnetRef, eventId) { + if (!dotnetRef || !eventId) { + return; + } + + let eventHandler = createThemeEventHandler(dotnetRef); + storeEventHandler(_colorThemekHandlerDict, eventId, eventHandler); + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', eventHandler); +} +export function removeColorThemeEvent(eventId) { + if (!eventId) { + return; + } + + let eventCallback = removeAndReturnEventHandler(_colorThemekHandlerDict, eventId); + + if (!eventCallback) { + return; //No event handler found + } + + window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', eventCallback); +} + +export function dispose(eventIdArray) { + if (eventIdArray) { + for (var i = 0; i < eventIdArray.length; i++) { + removeColorThemeEvent(eventIdArray[i]); + } + } +} + +//Store eventId with event +function storeEventHandler(dict, eventId, eventCallback) { + let elementFound = false; + for (let i = 0; i < dict.length; i++) { + if (dict[i].key === eventId) { + elementFound = true; + break; + } + } + + if (!elementFound) { + dict.push({ + key: eventId, + handler: eventCallback + }); + } +} +//Remove eventId with event +function removeAndReturnEventHandler(dict, eventId) { + let eventCallback; + + for (let i = 0; i < dict.length; i++) { + if (dict[i].key === eventId) { + eventCallback = dict[i].handler; //associate callback + dict.splice(i, 1); + break; + } + } + + return eventCallback; +} +let _colorThemekHandlerDict = []; + + +function getTheme() { + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + return 0; + } + + return 1; +} +function createThemeEventHandler(dotnetRef) { + let eventHandler = function () { + var theme = getTheme(); + dotnetRef.invokeMethodAsync("BrowserThemeChanged", theme); + } + + return eventHandler; +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/colorTheme.min.js b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/colorTheme.min.js new file mode 100644 index 00000000..ca73c989 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.Common.JsInterop/wwwroot/colorTheme.min.js @@ -0,0 +1 @@ +export function getColorTheme(){return t()}export function addColorThemeEvent(t,r){if(t&&r){let f=u(t);i(n,r,f);window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",f)}}export function removeColorThemeEvent(t){if(t){let i=r(n,t);i&&window.matchMedia("(prefers-color-scheme: dark)").removeEventListener("change",i)}}export function dispose(n){if(n)for(var t=0;t<n.length;t++)removeColorThemeEvent(n[t])}function i(n,t,i){let r=!1;for(let i=0;i<n.length;i++)if(n[i].key===t){r=!0;break}r||n.push({key:t,handler:i})}function r(n,t){let i;for(let r=0;r<n.length;r++)if(n[r].key===t){i=n[r].handler;n.splice(r,1);break}return i}function t(){return window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?0:1}function u(n){return function(){var i=t();n.invokeMethodAsync("BrowserThemeChanged",i)}}let n=[]; \ 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 10cf04aa..1f5198af 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor @@ -47,6 +47,7 @@ <li><NavLink href="jsinterop#clipboard-js">Clipboard Js</NavLink></li> <li><NavLink href="jsinterop#lang-js">Language Js</NavLink></li> <li><NavLink href="jsinterop#date-js">Browser Date Js</NavLink></li> + <li><NavLink href="jsinterop#theme-js">Browser Theme Js</NavLink></li> <li><NavLink href="jsinterop#geo-js">Geo Js</NavLink></li> <li><NavLink href="jsinterop#head-js">Head Js</NavLink></li> </ul> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor index 839156dc..ddabac18 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JSInterop.razor @@ -32,6 +32,7 @@ Smooth scroll: <li><NavLink href="jsinterop#clipboard-js">Clipboard Js</NavLink></li> <li><NavLink href="jsinterop#lang-js">Browser Language Js</NavLink></li> <li><NavLink href="jsinterop#date-js">Browser Date Js</NavLink></li> + <li><NavLink href="jsinterop#theme-js">Browser Theme Js</NavLink></li> <li><NavLink href="jsinterop#geo-js">Geolocation Js</NavLink></li> <li><NavLink href="jsinterop#head-js">Head Js</NavLink></li> </ul> @@ -55,6 +56,8 @@ Smooth scroll: <BrowserDateJs /> +<BrowserThemeJs /> + <GeoJs /> <HeadJs /> 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 e678cce7..038dc059 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserDateJs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserDateJs.razor @@ -3,7 +3,7 @@ <Content><h3>Browser Date JS</h3></Content> </PermaLinkElement> - <strong>Browser Date JS</strong> is a simple JS call to <code>new Date();</code> to retrieve client machine date and time. + <strong>Browser Date JS</strong> is an injectable <code>IBrowserDateService</code> service simple JS call to <code>new Date();</code> to retrieve client machine date and time. <AdvancedTimer IsEnabled="true" IntervalInMilisec="200" Occurring="Times.Infinite()" AutoStart="true" OnIntervalElapsed="@Clock" /> <p> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserThemeJs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserThemeJs.razor new file mode 100644 index 00000000..fb97d4c7 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/JsDemo/BrowserThemeJs.razor @@ -0,0 +1,58 @@ +<div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="theme-js" IconActions="PermaLinkIconActions.Copy | PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>Browser Theme JS</h3></Content> + </PermaLinkElement> + + <strong>Browser Theme JS</strong> is an injectable <code>IBrowserThemeService</code> to handle Browser color scheme queries and changes. + <p> + <span class="@(_theme == BrowserColorThemes.Dark ? "text-dark bg-light" : "text-light bg-dark")">Browser color theme is: <strong>@_theme.ToString()</strong></span> + </p> + + <div class="row pb-2"> + <div class="col-12"> + <button class="btn btn-primary" @onclick="ThemeEventHandler">@(_themeSubscribed ? "Unsubscribe from Browser theme Change" : "Subscribe to Browser theme Change")</button> + </div> + </div> + +</div> + +@implements IAsyncDisposable +@inject IBrowserThemeService _themeService + +@code { + private BrowserColorThemes _theme; + private string _themeEventId; + private bool _themeSubscribed; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + _theme = await _themeService.GetBrowserColorThemeAsync(); + StateHasChanged(); + } + } + + + private async Task ThemeEventHandler() + { + if (_themeSubscribed) + { + await _themeService.RemoveColorThemeChangeAsync(_themeEventId); + } + else + { + _themeEventId = await _themeService.RegisterColorThemeChangeAsync(async (val) => { _theme = val; }); + } + + _themeSubscribed = !_themeSubscribed; + } + + public async ValueTask DisposeAsync() + { + if (_themeService is not null) + { + await _themeService.DisposeAsync(); + } + } +} \ No newline at end of file From ccd1eab3b810063eec5a3aa49f27a8fa6e7dde93 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 5 Jul 2021 10:45:55 +0200 Subject: [PATCH 54/69] Created Majorsoft.Blazor.Components.GdprConsent project --- .../Component1.razor | 3 ++ .../Component1.razor.css | 6 +++ .../ExampleJsInterop.cs | 40 ++++++++++++++++++ ...rsoft.Blazor.Components.GdprConsent.csproj | 22 ++++++++++ .../_Imports.razor | 1 + .../wwwroot/background.png | Bin 0 -> 378 bytes .../wwwroot/exampleJsInterop.js | 6 +++ .../Components/Index.razor | 2 + .../Shared/NavMenu.razor | 5 +++ src/Majorsoft.Blazor.Components.sln | 7 +++ 10 files changed, 92 insertions(+) create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor.css create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/ExampleJsInterop.cs create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/background.png create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/exampleJsInterop.js diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor b/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor new file mode 100644 index 00000000..c0131066 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor @@ -0,0 +1,3 @@ +<div class="my-component"> + This Blazor component is defined in the <strong>Majorsoft.Blazor.Components.GdprConsent</strong> package. +</div> diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor.css b/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor.css new file mode 100644 index 00000000..c6afca40 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor.css @@ -0,0 +1,6 @@ +.my-component { + border: 2px dashed red; + padding: 1em; + margin: 1em 0; + background-image: url('background.png'); +} diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/ExampleJsInterop.cs b/src/Majorsoft.Blazor.Components.GdprConsent/ExampleJsInterop.cs new file mode 100644 index 00000000..891f9d2e --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/ExampleJsInterop.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; + +using Microsoft.JSInterop; + +namespace Majorsoft.Blazor.Components.GdprConsent +{ + // This class provides an example of how JavaScript functionality can be wrapped + // in a .NET class for easy consumption. The associated JavaScript module is + // loaded on demand when first needed. + // + // This class can be registered as scoped DI service and then injected into Blazor + // components for use. + + public class ExampleJsInterop : IAsyncDisposable + { + private readonly Lazy<Task<IJSObjectReference>> moduleTask; + + public ExampleJsInterop(IJSRuntime jsRuntime) + { + moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>( + "import", "./_content/Majorsoft.Blazor.Components.GdprConsent/exampleJsInterop.js").AsTask()); + } + + public async ValueTask<string> Prompt(string message) + { + var module = await moduleTask.Value; + return await module.InvokeAsync<string>("showPrompt", message); + } + + public async ValueTask DisposeAsync() + { + if (moduleTask.IsValueCreated) + { + var module = await moduleTask.Value; + await module.DisposeAsync(); + } + } + } +} diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj new file mode 100644 index 00000000..0d6560a8 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj @@ -0,0 +1,22 @@ +<Project Sdk="Microsoft.NET.Sdk.Razor"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + </PropertyGroup> + + + <ItemGroup> + <SupportedPlatform Include="browser" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.7" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Majorsoft.Blazor.Components.Modal\Majorsoft.Blazor.Components.Modal.csproj" /> + <ProjectReference Include="..\Majorsoft.Blazor.Components.Toggle\Majorsoft.Blazor.Components.Toggle.csproj" /> + <ProjectReference Include="..\Majorsoft.Blazor.Extensions.BrowserStorage\Majorsoft.Blazor.Extensions.BrowserStorage.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor b/src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor new file mode 100644 index 00000000..77285129 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor @@ -0,0 +1 @@ +@using Microsoft.AspNetCore.Components.Web diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/background.png b/src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/background.png new file mode 100644 index 0000000000000000000000000000000000000000..e15a3bde6e2bdb380df6a0b46d7ed00bdeb0aaa8 GIT binary patch literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^x**KK1SGdsl%54rjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucLCF%=h?3y^w370~qEv>0#LT=By}Z;C1rt33 zJwr2>%=KS^ie7oTIEF;HpS|GCbyPusHSqiXaCu3qf)82(9Gq&mZq2{Kq}M*X&MWtJ zSi1Jo7ZzfImg%g=t(qo=wsSR2lZoP(Rj#3wacN=q0?Br(rXzgZEGK2$ID{<T2$;Mw zaJ?|$u#{tMJ9Cp2pFGQ6%R2&JUVLx~xc#taLEB+@$8vX$CcQolmbrZnEV6wOEN}Y? zSeEK5Xvj|fBqCU2`0|3QfyLth_X#%=8x>|A=5S<cmu+&BUB)3*6rsH0eSt&3@rTU| z%-f}#<Yeb@WLeJ<yi)K(`Nca9#|uXVTNhjw>{xJEuzSH>!M+7wSY6hB<=-E^*n0W7 S8wY^CX7F_Nb6Mw<&;$S{dxtsz literal 0 HcmV?d00001 diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/exampleJsInterop.js b/src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/exampleJsInterop.js new file mode 100644 index 00000000..ea8d76ad --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/exampleJsInterop.js @@ -0,0 +1,6 @@ +// This is a JavaScript module that is loaded on demand. It can export any number of +// functions, and may import other JavaScript modules if required. + +export function showPrompt(message) { + return prompt(message, 'Type anything here'); +} diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor index 1f5198af..cb18d990 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Index.razor @@ -91,6 +91,8 @@ <li><NavLink href="collapse#accordion">Accordion</NavLink></li> </ul> </li> + <li><NavLink href="gdpr">GDPR Consents</NavLink></li> + @*<li><NavLink href="draganddrop">Drag and Drop</NavLink></li> <li><NavLink href="colorpicker">Color Picker</NavLink></li>*@ </ul> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor index f539cc92..6bb52983 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor @@ -98,6 +98,11 @@ <span class="fa fa-lg fa-chevron-circle-down" aria-hidden="true"></span> Collapse </NavLink> </li> + <li class="nav-item px-2"> + <NavLink class="nav-link" href="gdpr"> + <span class="fa fa-lg fa-cookie-bite" aria-hidden="true"></span> GDPR banners + </NavLink> + </li> <li class="nav-item px-2"> <NavLink class="nav-link" href="draganddrop"> <span class="fa fa-lg fa-arrows-alt" aria-hidden="true"></span> Drag and Drop diff --git a/src/Majorsoft.Blazor.Components.sln b/src/Majorsoft.Blazor.Components.sln index a8b8ef25..d6c5a091 100644 --- a/src/Majorsoft.Blazor.Components.sln +++ b/src/Majorsoft.Blazor.Components.sln @@ -91,6 +91,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Components EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Components.Inputs.Tests", "Majorsoft.Blazor.Components.Inputs.Tests\Majorsoft.Blazor.Components.Inputs.Tests.csproj", "{5A9DBE07-4666-4DEF-841A-477F2A1A28B9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Majorsoft.Blazor.Components.GdprConsent", "Majorsoft.Blazor.Components.GdprConsent\Majorsoft.Blazor.Components.GdprConsent.csproj", "{E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -233,6 +235,10 @@ Global {5A9DBE07-4666-4DEF-841A-477F2A1A28B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A9DBE07-4666-4DEF-841A-477F2A1A28B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A9DBE07-4666-4DEF-841A-477F2A1A28B9}.Release|Any CPU.Build.0 = Release|Any CPU + {E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -271,6 +277,7 @@ Global {261B879A-4E63-4D4B-9B59-E8C8F84531FF} = {121A4471-EF7A-4F9A-A856-67E8376A4AD2} {A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC} = {B6905E0B-759A-4928-B38A-9210B5CC8988} {5A9DBE07-4666-4DEF-841A-477F2A1A28B9} = {020CAF9C-D289-470A-BB97-E58D73DDCE70} + {E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC} = {B6905E0B-759A-4928-B38A-9210B5CC8988} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F3E8B7F7-3F7C-4D6C-8B7B-48F1AF4694B9} From 69dfc19718625b54f043b5c556ec4ae618b8eda5 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 5 Jul 2021 13:30:36 +0200 Subject: [PATCH 55/69] Created GDPR demo pages. --- .../Component1.razor | 3 -- .../Component1.razor.css | 6 --- .../ExampleJsInterop.cs | 40 --------------- ...rsoft.Blazor.Components.GdprConsent.csproj | 48 +++++++++++++++--- ...ajorsoft.Blazor.Components.GdprConsent.xml | 8 +++ .../wwwroot/background.png | Bin 378 -> 0 bytes .../wwwroot/exampleJsInterop.js | 6 --- ...jorsoft.Blazor.Components.PermaLink.csproj | 1 - ...Majorsoft.Blazor.Components.TestApp.csproj | 1 + .../Pages/GdprConsentPage.razor | 3 ++ .../Components/GdprConsents.razor | 5 ++ .../Pages/GdprConsentPage.razor | 3 ++ .../Shared/NavMenu.razor | 2 +- .../Pages/GdprConsentPage.razor | 3 ++ 14 files changed, 65 insertions(+), 64 deletions(-) delete mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor delete mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor.css delete mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/ExampleJsInterop.cs create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml delete mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/background.png delete mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/exampleJsInterop.js create mode 100644 src/Majorsoft.Blazor.Components.TestApp/Pages/GdprConsentPage.razor create mode 100644 src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor create mode 100644 src/Majorsoft.Blazor.Components.TestApps.Common/Pages/GdprConsentPage.razor create mode 100644 src/Majorsoft.Blazor.Components.TestServerApp/Pages/GdprConsentPage.razor diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor b/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor deleted file mode 100644 index c0131066..00000000 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor +++ /dev/null @@ -1,3 +0,0 @@ -<div class="my-component"> - This Blazor component is defined in the <strong>Majorsoft.Blazor.Components.GdprConsent</strong> package. -</div> diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor.css b/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor.css deleted file mode 100644 index c6afca40..00000000 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Component1.razor.css +++ /dev/null @@ -1,6 +0,0 @@ -.my-component { - border: 2px dashed red; - padding: 1em; - margin: 1em 0; - background-image: url('background.png'); -} diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/ExampleJsInterop.cs b/src/Majorsoft.Blazor.Components.GdprConsent/ExampleJsInterop.cs deleted file mode 100644 index 891f9d2e..00000000 --- a/src/Majorsoft.Blazor.Components.GdprConsent/ExampleJsInterop.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Threading.Tasks; - -using Microsoft.JSInterop; - -namespace Majorsoft.Blazor.Components.GdprConsent -{ - // This class provides an example of how JavaScript functionality can be wrapped - // in a .NET class for easy consumption. The associated JavaScript module is - // loaded on demand when first needed. - // - // This class can be registered as scoped DI service and then injected into Blazor - // components for use. - - public class ExampleJsInterop : IAsyncDisposable - { - private readonly Lazy<Task<IJSObjectReference>> moduleTask; - - public ExampleJsInterop(IJSRuntime jsRuntime) - { - moduleTask = new(() => jsRuntime.InvokeAsync<IJSObjectReference>( - "import", "./_content/Majorsoft.Blazor.Components.GdprConsent/exampleJsInterop.js").AsTask()); - } - - public async ValueTask<string> Prompt(string message) - { - var module = await moduleTask.Value; - return await module.InvokeAsync<string>("showPrompt", message); - } - - public async ValueTask DisposeAsync() - { - if (moduleTask.IsValueCreated) - { - var module = await moduleTask.Value; - await module.DisposeAsync(); - } - } - } -} diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj index 0d6560a8..33b9e63d 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj @@ -1,22 +1,56 @@ -<Project Sdk="Microsoft.NET.Sdk.Razor"> +<Project Sdk="Microsoft.NET.Sdk.Razor"> <PropertyGroup> - <TargetFramework>net5.0</TargetFramework> + <TargetFramework>net5.0</TargetFramework> + <nullable>enable</nullable> + <GeneratePackageOnBuild>true</GeneratePackageOnBuild> + <PackageId>Majorsoft.Blazor.Components.GdprConsent</PackageId> + <Version>1.0.0.0</Version> + <Authors>Imre Toth</Authors> + <Company>Majorsoft</Company> + <Product>Blazor Components</Product> + <Copyright>©2021 Imre Toth</Copyright> + <PackageLicenseFile>License.txt</PackageLicenseFile> + <PackageProjectUrl>https://github.com/majorimi/blazor-components/blob/master/.github/docs/Gdpr.md</PackageProjectUrl> + <PackageIcon>blazor.components.png</PackageIcon> + <RepositoryUrl>https://github.com/majorimi/blazor-components</RepositoryUrl> + <RepositoryType>Git</RepositoryType> + <PackageTags>.Net5 Blazor GDPR Consent Law Banner ModalWindow Modal Cookie Cookies</PackageTags> + <Title>Blazor Components GDPR Consent banner and popup</Title> + <Description>Blazor component that renders a customizable . Part of Majorsoft Blazor library.</Description> </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <DocumentationFile>.\Majorsoft.Blazor.Components.GdprConsent.xml</DocumentationFile> + </PropertyGroup> <ItemGroup> - <SupportedPlatform Include="browser" /> + <SupportedPlatform Include="browser" /> </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.7" /> + <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.3" /> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\Majorsoft.Blazor.Components.Modal\Majorsoft.Blazor.Components.Modal.csproj" /> - <ProjectReference Include="..\Majorsoft.Blazor.Components.Toggle\Majorsoft.Blazor.Components.Toggle.csproj" /> - <ProjectReference Include="..\Majorsoft.Blazor.Extensions.BrowserStorage\Majorsoft.Blazor.Extensions.BrowserStorage.csproj" /> + <ProjectReference Include="..\Majorsoft.Blazor.Components.Modal\Majorsoft.Blazor.Components.Modal.csproj" /> + <ProjectReference Include="..\Majorsoft.Blazor.Components.Toggle\Majorsoft.Blazor.Components.Toggle.csproj" /> + <ProjectReference Include="..\Majorsoft.Blazor.Extensions.BrowserStorage\Majorsoft.Blazor.Extensions.BrowserStorage.csproj" /> + </ItemGroup> + + <ItemGroup> + <Folder Include="wwwroot\" /> + </ItemGroup> + + <ItemGroup> + <None Include="..\..\.github\Images\blazor.components.png"> + <Pack>True</Pack> + <PackagePath></PackagePath> + </None> + <None Include="..\..\.github\License.txt"> + <Pack>True</Pack> + <PackagePath></PackagePath> + </None> </ItemGroup> </Project> diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml new file mode 100644 index 00000000..f8a89f4a --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<doc> + <assembly> + <name>Majorsoft.Blazor.Components.GdprConsent</name> + </assembly> + <members> + </members> +</doc> diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/background.png b/src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/background.png deleted file mode 100644 index e15a3bde6e2bdb380df6a0b46d7ed00bdeb0aaa8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^x**KK1SGdsl%54rjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucLCF%=h?3y^w370~qEv>0#LT=By}Z;C1rt33 zJwr2>%=KS^ie7oTIEF;HpS|GCbyPusHSqiXaCu3qf)82(9Gq&mZq2{Kq}M*X&MWtJ zSi1Jo7ZzfImg%g=t(qo=wsSR2lZoP(Rj#3wacN=q0?Br(rXzgZEGK2$ID{<T2$;Mw zaJ?|$u#{tMJ9Cp2pFGQ6%R2&JUVLx~xc#taLEB+@$8vX$CcQolmbrZnEV6wOEN}Y? zSeEK5Xvj|fBqCU2`0|3QfyLth_X#%=8x>|A=5S<cmu+&BUB)3*6rsH0eSt&3@rTU| z%-f}#<Yeb@WLeJ<yi)K(`Nca9#|uXVTNhjw>{xJEuzSH>!M+7wSY6hB<=-E^*n0W7 S8wY^CX7F_Nb6Mw<&;$S{dxtsz diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/exampleJsInterop.js b/src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/exampleJsInterop.js deleted file mode 100644 index ea8d76ad..00000000 --- a/src/Majorsoft.Blazor.Components.GdprConsent/wwwroot/exampleJsInterop.js +++ /dev/null @@ -1,6 +0,0 @@ -// This is a JavaScript module that is loaded on demand. It can export any number of -// functions, and may import other JavaScript modules if required. - -export function showPrompt(message) { - return prompt(message, 'Type anything here'); -} diff --git a/src/Majorsoft.Blazor.Components.PermaLink/Majorsoft.Blazor.Components.PermaLink.csproj b/src/Majorsoft.Blazor.Components.PermaLink/Majorsoft.Blazor.Components.PermaLink.csproj index d5ce9da5..a40b4469 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/Majorsoft.Blazor.Components.PermaLink.csproj +++ b/src/Majorsoft.Blazor.Components.PermaLink/Majorsoft.Blazor.Components.PermaLink.csproj @@ -10,7 +10,6 @@ <Company>Majorsoft</Company> <Product>Blazor Components</Product> <Copyright>©2021 Imre Toth</Copyright> - <PackageLicenseExpression></PackageLicenseExpression> <PackageProjectUrl>https://github.com/majorimi/blazor-components/blob/master/.github/docs/PermaLink.md</PackageProjectUrl> <PackageIcon>blazor.components.png</PackageIcon> <RepositoryUrl>https://github.com/majorimi/blazor-components</RepositoryUrl> diff --git a/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj b/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj index 26cd7115..490f3efd 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj +++ b/src/Majorsoft.Blazor.Components.TestApp/Majorsoft.Blazor.Components.TestApp.csproj @@ -23,6 +23,7 @@ <ItemGroup> <Watch Remove="Pages\ColorPickerPage.razor" /> <Watch Remove="Pages\DragAndDropPage.razor" /> + <Watch Remove="Pages\GdprConsentPage.razor" /> <Watch Remove="Pages\IndexPage.razor" /> <Watch Remove="Pages\JSInteropPage.razor" /> <Watch Remove="Pages\LoadingPage.razor" /> diff --git a/src/Majorsoft.Blazor.Components.TestApp/Pages/GdprConsentPage.razor b/src/Majorsoft.Blazor.Components.TestApp/Pages/GdprConsentPage.razor new file mode 100644 index 00000000..92e71ab7 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApp/Pages/GdprConsentPage.razor @@ -0,0 +1,3 @@ +@page "/gdpr" + +<GdprConsents /> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor new file mode 100644 index 00000000..9b4c6315 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor @@ -0,0 +1,5 @@ +<h3>GdprConsents</h3> + +@code { + +} diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/GdprConsentPage.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/GdprConsentPage.razor new file mode 100644 index 00000000..92e71ab7 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Pages/GdprConsentPage.razor @@ -0,0 +1,3 @@ +@page "/gdpr" + +<GdprConsents /> \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor index 6bb52983..7e651261 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Shared/NavMenu.razor @@ -100,7 +100,7 @@ </li> <li class="nav-item px-2"> <NavLink class="nav-link" href="gdpr"> - <span class="fa fa-lg fa-cookie-bite" aria-hidden="true"></span> GDPR banners + <span class="fa fa-lg fa-cookie-bite" aria-hidden="true"></span> GDPR Consents </NavLink> </li> <li class="nav-item px-2"> diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Pages/GdprConsentPage.razor b/src/Majorsoft.Blazor.Components.TestServerApp/Pages/GdprConsentPage.razor new file mode 100644 index 00000000..92e71ab7 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Pages/GdprConsentPage.razor @@ -0,0 +1,3 @@ +@page "/gdpr" + +<GdprConsents /> \ No newline at end of file From fecf049996fcbf71f8d4d27cb03a30cb28d84f68 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Thu, 8 Jul 2021 22:48:36 +0200 Subject: [PATCH 56/69] Started to implement GdprBanner component. --- .../GdprBanner.razor | 50 ++++++++++++++ .../GdprBanner.razor.css | 13 ++++ .../GdprBanner.razor.min.css | 1 + .../GdprConsentExtension.cs | 31 +++++++++ .../IGdprConsentService.cs | 39 +++++++++++ ...rsoft.Blazor.Components.GdprConsent.csproj | 22 ++++++- ...ajorsoft.Blazor.Components.GdprConsent.xml | 46 +++++++++++++ .../_Imports.razor | 4 +- .../bundleconfig.json | 8 +++ .../Program.cs | 3 + .../Components/GdprConsents.razor | 65 ++++++++++++++++++- .../Components/Loading.razor | 4 +- ...t.Blazor.Components.TestApps.Common.csproj | 1 + .../_Imports.razor | 4 +- .../Startup.cs | 3 + src/Majorsoft.Blazor.Components.sln | 2 +- 16 files changed, 288 insertions(+), 8 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor.css create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor.min.css create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/bundleconfig.json diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor new file mode 100644 index 00000000..d8246370 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor @@ -0,0 +1,50 @@ +<div class="banner" @ref="_inputRef" + style="background-color: rgba(@BannerBackgroundColor, @BannerOpacity.ToString("0.00", CultureInfo.InvariantCulture))" + @attributes=AllOtherAttributes> + @Content +</div> + +@using System.Globalization +@using Majorsoft.Blazor.Components.Core.HtmlColors +@inject ILogger<GdprBanner> _logger +@inject IGdprConsentService _gdprConsentService + +@code { + private ElementReference _inputRef; + /// <summary> + /// Exposes a Blazor ElementReference of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + /// </summary> + public ElementReference InnerElementReference => _inputRef; + + /// <summary> + /// HTML Content of the collapse panel. + /// </summary> + [Parameter] public RenderFragment Content { get; set; } + + private string _bannerColor = "128,128,128";//gray + /// <summary> + /// Sets the style of the HTML div background-color. Use HTML specified: Color Names, RGB, HEX or with HSL values. + /// </summary> + [Parameter] + public string BannerBackgroundColor + { + get => _bannerColor; + set => _bannerColor = new HtmlColor(value)?.RgbColor.ToRgbString(); + } + + /// <summary> + /// Opacity of the overlay div. Value should be between 0..1. Where 0 means the overlay layer is not visible. + /// </summary> + [Parameter] public double BannerOpacity { get; set; } = 0.9; + + /// <summary> + /// + /// </summary> + [Parameter] public DateTime AnswerValidUntil { get; set; } = DateTime.Now.AddMonths(1); + + /// <summary> + /// Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. + /// </summary> + [Parameter(CaptureUnmatchedValues = true)] + public Dictionary<string, object> AllOtherAttributes { get; set; } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor.css b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor.css new file mode 100644 index 00000000..6a6b83c5 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor.css @@ -0,0 +1,13 @@ +.banner { + width: 100%; + /*border: 1px solid #e0e0e0;*/ + bottom: 0; + left: 0; + position: fixed; + display: block; + z-index: 1000; + /* text-align: center; */ + overflow-x: hidden; + overflow-y: auto; + outline: 0; +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor.min.css b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor.min.css new file mode 100644 index 00000000..e345fc72 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor.min.css @@ -0,0 +1 @@ +.banner{width:100%;bottom:0;left:0;position:fixed;display:block;z-index:1000;overflow-x:hidden;overflow-y:auto;outline:0} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs new file mode 100644 index 00000000..086000cf --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs @@ -0,0 +1,31 @@ +using System; + +using Majorsoft.Blazor.Extensions.BrowserStorage; + +using Microsoft.Extensions.DependencyInjection; + +namespace Majorsoft.Blazor.Components.GdprConsent +{ + /// <summary> + /// Extension methods to register required JS Interop services into IServiceCollection + /// </summary> + public static class GdprConsentExtension + { + /// <summary> + /// Registers required JS Interop services into IServiceCollection + /// </summary> + /// <param name="services">IServiceCollection instance</param> + public static IServiceCollection AddGdprConsent(this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.AddBrowserStorage(); + services.AddTransient<IGdprConsentService, GdprConsentService>(); + + return services; + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs new file mode 100644 index 00000000..96034888 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Majorsoft.Blazor.Extensions.BrowserStorage; + +namespace Majorsoft.Blazor.Components.GdprConsent +{ + /// <summary> + /// + /// </summary> + public interface IGdprConsentService + { + ValueTask<GdprConsentData> GetGdprConsentDataAsync(); + } + + public class GdprConsentService : IGdprConsentService + { + private readonly ILocalStorageService _storageService; + + public GdprConsentService(ILocalStorageService storageService) + { + _storageService = storageService; + } + + public ValueTask<GdprConsentData> GetGdprConsentDataAsync() => throw new NotImplementedException(); + } + + public class GdprConsentData + { + public bool IsAccepted { get; set; } + + public DateTime AnsweredAt { get; set; } + + public DateTime AnswerValidUntil { get; set; } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj index 33b9e63d..b3c8dff0 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj @@ -17,12 +17,20 @@ <RepositoryType>Git</RepositoryType> <PackageTags>.Net5 Blazor GDPR Consent Law Banner ModalWindow Modal Cookie Cookies</PackageTags> <Title>Blazor Components GDPR Consent banner and popup</Title> - <Description>Blazor component that renders a customizable . Part of Majorsoft Blazor library.</Description> + <Description>Blazor component that renders a customizable GDPR consent Banner or Popup witch Accept/Reject for cookie settings chosen value is persisted to Browser storage. Part of Majorsoft Blazor library.</Description> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <DocumentationFile>.\Majorsoft.Blazor.Components.GdprConsent.xml</DocumentationFile> </PropertyGroup> + + <ItemGroup> + <Content Remove="bundleconfig.json" /> + </ItemGroup> + + <ItemGroup> + <_ContentIncludedByDefault Remove="bundleconfig.json" /> + </ItemGroup> <ItemGroup> <SupportedPlatform Include="browser" /> @@ -51,6 +59,18 @@ <Pack>True</Pack> <PackagePath></PackagePath> </None> + <None Include="bundleconfig.json" /> </ItemGroup> + <!--Include project DLL output to Nuget package--> + <ItemGroup> + <ProjectReference PrivateAssets="all" Include="..\Majorsoft.Blazor.Components.Core\Majorsoft.Blazor.Components.Core.csproj" /> + </ItemGroup> + <ItemGroup> + <_PackageFiles Include="$(OutputPath)\Majorsoft.Blazor.Components.Core.dll"> + <BuildAction>None</BuildAction> + <PackagePath>lib\net5.0</PackagePath> + </_PackageFiles> + </ItemGroup> + </Project> diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml index f8a89f4a..86285b61 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml @@ -4,5 +4,51 @@ <name>Majorsoft.Blazor.Components.GdprConsent</name> </assembly> <members> + <member name="T:Majorsoft.Blazor.Components.GdprConsent.GdprConsentExtension"> + <summary> + Extension methods to register required JS Interop services into IServiceCollection + </summary> + </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprConsentExtension.AddGdprConsent(Microsoft.Extensions.DependencyInjection.IServiceCollection)"> + <summary> + Registers required JS Interop services into IServiceCollection + </summary> + <param name="services">IServiceCollection instance</param> + </member> + <member name="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService"> + <summary> + + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.InnerElementReference"> + <summary> + Exposes a Blazor ElementReference of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.Content"> + <summary> + HTML Content of the collapse panel. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.BannerBackgroundColor"> + <summary> + Sets the style of the HTML div background-color. Use HTML specified: Color Names, RGB, HEX or with HSL values. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.BannerOpacity"> + <summary> + Opacity of the overlay div. Value should be between 0..1. Where 0 means the overlay layer is not visible. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.AnswerValidUntil"> + <summary> + + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.AllOtherAttributes"> + <summary> + Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. + </summary> + </member> </members> </doc> diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor b/src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor index 77285129..848647bc 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor @@ -1 +1,3 @@ -@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.Extensions.Logging; \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/bundleconfig.json b/src/Majorsoft.Blazor.Components.GdprConsent/bundleconfig.json new file mode 100644 index 00000000..930e390a --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/bundleconfig.json @@ -0,0 +1,8 @@ +[ + { + "outputFileName": "GdprBanner.razor.min.css", + "inputFiles": [ + "GdprBanner.razor.css" + ] + } +] diff --git a/src/Majorsoft.Blazor.Components.TestApp/Program.cs b/src/Majorsoft.Blazor.Components.TestApp/Program.cs index 6ac84a0b..895b0f7d 100644 --- a/src/Majorsoft.Blazor.Components.TestApp/Program.cs +++ b/src/Majorsoft.Blazor.Components.TestApp/Program.cs @@ -12,6 +12,7 @@ using Majorsoft.Blazor.Components.Maps; using Majorsoft.Blazor.Extensions.BrowserStorage; using Majorsoft.Blazor.Extensions.Analytics; +using Majorsoft.Blazor.Components.GdprConsent; namespace Majorsoft.Blazor.Components.TestApp { @@ -31,6 +32,8 @@ public static async Task Main(string[] args) builder.Services.AddGoogleAnalytics(); + builder.Services.AddGdprConsent(); + builder.Logging.AddBrowserConsole() .SetMinimumLevel(LogLevel.Debug).AddFilter("Microsoft", LogLevel.Information); diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor index 9b4c6315..3e861be6 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor @@ -1,5 +1,66 @@ -<h3>GdprConsents</h3> +<PageScroll /> + +<h1>GDPR Consents</h1> +<p> + Blazor component that renders a customizable GDPR consent Banner or Popup witch Accept/Reject for cookie settings chosen value is persisted to Browser storage. For usage see source code and docs on + <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/GdprConsent.md" target="_blank">Github</a>. + <br /><strong>Majorsoft.Blazor.Components.GdprConsent</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.GdprConsent" target="_blank">Nuget</a> +</p> + +<div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="loading-page" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>Loading page</h3></Content> + </PermaLinkElement> + <p>Renders an Overlay layer for the whole page with customizable content for showing Page loading...</p> + + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Overlay color (Name, RGB, Hex, HSL): <input class="form-control w-100" @bind="_bannerColor" /> + </div> + </div> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Overlay opacity: @(_bannerOpacity / 1000) + <input type="range" class="w-100" min="0" max="1000" @bind="_bannerOpacity" @oninput="(e => _bannerOpacity = int.Parse(e.Value?.ToString()))" /> + </div> + </div> + +</div> + +<style> + .gdpr-banner { + font-size: medium; + font-weight: 500; + padding: 16px 0; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + /*border: 1px solid #e0e0e0;*/ + } +</style> +<GdprBanner BannerOpacity="@(_bannerOpacity / 1000)" BannerBackgroundColor="@_bannerColor"> + <Content> + <div class="gdpr-banner"> + <span class="fa fa-lg fa-cookie-bite" aria-hidden="true"></span> + This demo site actually does NOT uses cookies. Only demonstrate Cookie consent banner usage in your Blazor Application. + + <button type="button" class="btn btn-primary m-1">I agree</button> + <button type="button" class="btn btn-secondary m-1">Disagree</button> + </div> + </Content> +</GdprBanner> @code { + //GDPR banner + private string _bannerColor = "lightblue"; + private double _bannerOpacity = 900; -} + //GDPR popup + private string _overlayColor = "lightblue"; + private double _overlayOpacity = 700; +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor index f19796e4..c494faa4 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor @@ -38,7 +38,7 @@ </div> </div> - <LoadingPage IsLoading="@_pageIsLoading" OverlayBackgroundColor="@_overlayColor" OverlayOpacity="@(_overlayOpacity/1000)"> + <LoadingPage IsLoading="@_pageIsLoading" OverlayBackgroundColor="@_overlayColor" OverlayOpacity="@(_overlayOpacity / 1000)"> <LoadingContent> @((MarkupString)_overlayContent) </LoadingContent> @@ -70,7 +70,7 @@ </div> </div> - <LoadingElement IsLoading="@_elementIsLoading" OverlayBackgroundColor="@_overlayColorTable" OverlayOpacity="@(_overlayOpacityTable/1000)"> + <LoadingElement IsLoading="@_elementIsLoading" OverlayBackgroundColor="@_overlayColorTable" OverlayOpacity="@(_overlayOpacityTable / 1000)"> <LoadingContent> @((MarkupString)_overlayContent2) </LoadingContent> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj b/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj index f9a7f3a9..0d41dbcf 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Majorsoft.Blazor.Components.TestApps.Common.csproj @@ -29,6 +29,7 @@ <ProjectReference Include="..\Majorsoft.Blazor.Components.Core\Majorsoft.Blazor.Components.Core.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.CssEvents\Majorsoft.Blazor.Components.CssEvents.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Debounce\Majorsoft.Blazor.Components.Debounce.csproj" /> + <ProjectReference Include="..\Majorsoft.Blazor.Components.GdprConsent\Majorsoft.Blazor.Components.GdprConsent.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Inputs\Majorsoft.Blazor.Components.Inputs.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Loading\Majorsoft.Blazor.Components.Loading.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Maps\Majorsoft.Blazor.Components.Maps.csproj" /> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor index 5ac86437..2f7430b3 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/_Imports.razor @@ -53,4 +53,6 @@ @using Majorsoft.Blazor.Extensions.Analytics.Google -@using Majorsoft.Blazor.Components.Inputs \ No newline at end of file +@using Majorsoft.Blazor.Components.Inputs + +@using Majorsoft.Blazor.Components.GdprConsent \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs b/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs index 4456c637..f5e4a208 100644 --- a/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs +++ b/src/Majorsoft.Blazor.Components.TestServerApp/Startup.cs @@ -12,6 +12,7 @@ using Majorsoft.Blazor.Components.Maps; using Majorsoft.Blazor.Extensions.BrowserStorage; using Majorsoft.Blazor.Extensions.Analytics; +using Majorsoft.Blazor.Components.GdprConsent; namespace Majorsoft.Blazor.Components.TestServerApp { @@ -38,6 +39,8 @@ public void ConfigureServices(IServiceCollection services) services.AddBrowserStorage(); services.AddGoogleAnalytics(); + + services.AddGdprConsent(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Majorsoft.Blazor.Components.sln b/src/Majorsoft.Blazor.Components.sln index d6c5a091..1a4f5bb9 100644 --- a/src/Majorsoft.Blazor.Components.sln +++ b/src/Majorsoft.Blazor.Components.sln @@ -91,7 +91,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Components EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Components.Inputs.Tests", "Majorsoft.Blazor.Components.Inputs.Tests\Majorsoft.Blazor.Components.Inputs.Tests.csproj", "{5A9DBE07-4666-4DEF-841A-477F2A1A28B9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Majorsoft.Blazor.Components.GdprConsent", "Majorsoft.Blazor.Components.GdprConsent\Majorsoft.Blazor.Components.GdprConsent.csproj", "{E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Components.GdprConsent", "Majorsoft.Blazor.Components.GdprConsent\Majorsoft.Blazor.Components.GdprConsent.csproj", "{E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From 1a632f13d3a9a1556c13205dec82429cf5123511 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Thu, 8 Jul 2021 22:51:47 +0200 Subject: [PATCH 57/69] Update rgba() alpha channel digits --- .../LoadingElementTest.cs | 2 +- .../LoadingPageTest.cs | 2 +- .../LoadingElement.razor | 2 +- .../LoadingPage.razor | 2 +- .../ToggleSwitchTest.cs | 10 +++++----- .../ToggleSwitch.razor | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Loading.Tests/LoadingElementTest.cs b/src/Majorsoft.Blazor.Components.Loading.Tests/LoadingElementTest.cs index 87e9b178..6c61bdf0 100644 --- a/src/Majorsoft.Blazor.Components.Loading.Tests/LoadingElementTest.cs +++ b/src/Majorsoft.Blazor.Components.Loading.Tests/LoadingElementTest.cs @@ -89,7 +89,7 @@ public void LoadingElement_should_rendered_correctly_rounded_opacity() var div = rendered.Find("div"); Assert.IsNotNull(div); - rendered.MarkupMatches(@"<div class=""loading"" style=""background-color: rgba(128, 128, 128, 0.1)""><div class=""loading-content"">loading...</div><div>Content</div></div>"); + rendered.MarkupMatches(@"<div class=""loading"" style=""background-color: rgba(128, 128, 128, 0.12)""><div class=""loading-content"">loading...</div><div>Content</div></div>"); } [TestMethod] diff --git a/src/Majorsoft.Blazor.Components.Loading.Tests/LoadingPageTest.cs b/src/Majorsoft.Blazor.Components.Loading.Tests/LoadingPageTest.cs index 517ccc16..37a1111b 100644 --- a/src/Majorsoft.Blazor.Components.Loading.Tests/LoadingPageTest.cs +++ b/src/Majorsoft.Blazor.Components.Loading.Tests/LoadingPageTest.cs @@ -97,7 +97,7 @@ public void LoadingPage_should_rendered_correctly_rounded_opacity() var div = rendered.Find("div"); Assert.IsNotNull(div); - rendered.MarkupMatches(@"<div class=""loading"" style=""background-color: rgba(128, 128, 128, 0.1)""><div class=""loading-content"">loading...</div></div>"); + rendered.MarkupMatches(@"<div class=""loading"" style=""background-color: rgba(128, 128, 128, 0.12)""><div class=""loading-content"">loading...</div></div>"); } [TestMethod] diff --git a/src/Majorsoft.Blazor.Components.Loading/LoadingElement.razor b/src/Majorsoft.Blazor.Components.Loading/LoadingElement.razor index e6eb3c36..1a302afa 100644 --- a/src/Majorsoft.Blazor.Components.Loading/LoadingElement.razor +++ b/src/Majorsoft.Blazor.Components.Loading/LoadingElement.razor @@ -1,7 +1,7 @@ @if (_isLoading) { <div class="loading" - style="background-color: rgba(@OverlayBackgroundColor, @OverlayOpacity.ToString("0.0", CultureInfo.InvariantCulture))" + style="background-color: rgba(@OverlayBackgroundColor, @OverlayOpacity.ToString("0.00", CultureInfo.InvariantCulture))" @attributes=AllOtherAttributes> <div class="loading-content">@(LoadingContent)</div> @Content diff --git a/src/Majorsoft.Blazor.Components.Loading/LoadingPage.razor b/src/Majorsoft.Blazor.Components.Loading/LoadingPage.razor index 38dc0c53..d6872592 100644 --- a/src/Majorsoft.Blazor.Components.Loading/LoadingPage.razor +++ b/src/Majorsoft.Blazor.Components.Loading/LoadingPage.razor @@ -1,7 +1,7 @@ @if (_isLoading) { <div class="loading" - style="background-color: rgba(@OverlayBackgroundColor, @OverlayOpacity.ToString("0.0", CultureInfo.InvariantCulture))" + style="background-color: rgba(@OverlayBackgroundColor, @OverlayOpacity.ToString("0.00", CultureInfo.InvariantCulture))" @attributes=AllOtherAttributes> <div class="loading-content">@(LoadingContent)</div> </div> diff --git a/src/Majorsoft.Blazor.Components.Toggle.Tests/ToggleSwitchTest.cs b/src/Majorsoft.Blazor.Components.Toggle.Tests/ToggleSwitchTest.cs index 09384d52..fffadfdd 100644 --- a/src/Majorsoft.Blazor.Components.Toggle.Tests/ToggleSwitchTest.cs +++ b/src/Majorsoft.Blazor.Components.Toggle.Tests/ToggleSwitchTest.cs @@ -56,7 +56,7 @@ public void ToggleSwitch_should_rendered_correctly_value_true() Assert.IsFalse(input.HasAttribute("disabled")); var style = input.GetAttribute("style"); - Assert.AreEqual("width:80px; height:30px; cursor: pointer; border-radius: 15px; background-color: rgba(0, 0, 255, 0.5); ", style); + Assert.AreEqual("width:80px; height:30px; cursor: pointer; border-radius: 15px; background-color: rgba(0, 0, 255, 0.50); ", style); } [TestMethod] @@ -95,7 +95,7 @@ public void ToggleSwitch_should_rendered_correctly_width() Assert.IsNotNull(input); var style = input.GetAttribute("style"); - Assert.AreEqual("width:110px; height:30px; cursor: pointer; border-radius: 15px; background-color: rgba(0, 0, 255, 0.5); ", style); + Assert.AreEqual("width:110px; height:30px; cursor: pointer; border-radius: 15px; background-color: rgba(0, 0, 255, 0.50); ", style); } [TestMethod] @@ -108,7 +108,7 @@ public void ToggleSwitch_should_rendered_correctly_height() Assert.IsNotNull(input); var style = input.GetAttribute("style"); - Assert.AreEqual("width:80px; height:110px; cursor: pointer; border-radius: 55px; background-color: rgba(0, 0, 255, 0.5); ", style); + Assert.AreEqual("width:80px; height:110px; cursor: pointer; border-radius: 55px; background-color: rgba(0, 0, 255, 0.50); ", style); } [TestMethod] @@ -121,7 +121,7 @@ public void ToggleSwitch_should_rendered_correctly_onColor() Assert.IsNotNull(input); var style = input.GetAttribute("style"); - Assert.AreEqual("width:80px; height:30px; cursor: pointer; border-radius: 15px; background-color: rgba(255, 0, 0, 0.5); ", style); + Assert.AreEqual("width:80px; height:30px; cursor: pointer; border-radius: 15px; background-color: rgba(255, 0, 0, 0.50); ", style); } [TestMethod] @@ -135,7 +135,7 @@ public void ToggleSwitch_should_rendered_correctly_offColor() Assert.IsNotNull(input); var style = input.GetAttribute("style"); - Assert.AreEqual("width:80px; height:30px; cursor: pointer; border-radius: 15px; background-color: rgba(240, 240, 240, 0.5); ", style); + Assert.AreEqual("width:80px; height:30px; cursor: pointer; border-radius: 15px; background-color: rgba(240, 240, 240, 0.50); ", style); } [TestMethod] diff --git a/src/Majorsoft.Blazor.Components.Toggle/ToggleSwitch.razor b/src/Majorsoft.Blazor.Components.Toggle/ToggleSwitch.razor index d1d3ded5..90ef08eb 100644 --- a/src/Majorsoft.Blazor.Components.Toggle/ToggleSwitch.razor +++ b/src/Majorsoft.Blazor.Components.Toggle/ToggleSwitch.razor @@ -98,7 +98,7 @@ color = "230, 230, 230";//gray } - return $"background-color: rgba({color}, {alpha.ToString("0.0", CultureInfo.InvariantCulture)}); "; + return $"background-color: rgba({color}, {alpha.ToString("0.00", CultureInfo.InvariantCulture)}); "; } private string GetThumbStyle() { From ec04c5862d5d5bc44c82c86ccd249880dc851e18 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Sat, 10 Jul 2021 11:43:38 +0200 Subject: [PATCH 58/69] Changed opacity settings to 2 decimal. --- .../Components/Dialog.razor | 8 ++++---- .../Components/GdprConsents.razor | 10 +++++----- .../Components/Loading.razor | 16 ++++++++-------- .../Components/Tabs.razor | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor index 77cfbac2..f8d5c2e6 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Dialog.razor @@ -88,8 +88,8 @@ </div> <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - Overlay opacity: @(_overlayOpacity/1000) - <input type="range" class="w-100" min="0" max="1000" @bind="_overlayOpacity" @oninput="(e => _overlayOpacity = int.Parse(e.Value?.ToString()))" /> + Overlay opacity: @(_overlayOpacity/100) + <input type="range" class="w-100" min="0" max="100" @bind="_overlayOpacity" @oninput="(e => _overlayOpacity = int.Parse(e.Value?.ToString()))" /> </div> </div> <div class="row pb-2"> @@ -142,7 +142,7 @@ <ModalDialog @ref="_dialog" OverlayBackgroundColor="@_overlayColor" - OverlayOpacity="@(_overlayOpacity/1000)" + OverlayOpacity="@(_overlayOpacity/100)" Height="@_modalHeight" Width="@_modalWitdth" MinHeight="@_modalMinHeight" @@ -207,7 +207,7 @@ //Fully customized dialog private string _overlayColor = "128,128,128"; - private double _overlayOpacity = 500; + private double _overlayOpacity = 50; private double _modalHeight = 270; private double _modalWitdth = 500; private double _modalMinHeight = 100; diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor index 3e861be6..9070cee4 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor @@ -21,8 +21,8 @@ </div> <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - Overlay opacity: @(_bannerOpacity / 1000) - <input type="range" class="w-100" min="0" max="1000" @bind="_bannerOpacity" @oninput="(e => _bannerOpacity = int.Parse(e.Value?.ToString()))" /> + Overlay opacity: @(_bannerOpacity / 100) + <input type="range" class="w-100" min="0" max="100" @bind="_bannerOpacity" @oninput="(e => _bannerOpacity = int.Parse(e.Value?.ToString()))" /> </div> </div> @@ -43,7 +43,7 @@ /*border: 1px solid #e0e0e0;*/ } </style> -<GdprBanner BannerOpacity="@(_bannerOpacity / 1000)" BannerBackgroundColor="@_bannerColor"> +<GdprBanner BannerOpacity="@(_bannerOpacity / 100)" BannerBackgroundColor="@_bannerColor"> <Content> <div class="gdpr-banner"> <span class="fa fa-lg fa-cookie-bite" aria-hidden="true"></span> @@ -58,9 +58,9 @@ @code { //GDPR banner private string _bannerColor = "lightblue"; - private double _bannerOpacity = 900; + private double _bannerOpacity = 90; //GDPR popup private string _overlayColor = "lightblue"; - private double _overlayOpacity = 700; + private double _overlayOpacity = 70; } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor index c494faa4..a868c345 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Loading.razor @@ -28,8 +28,8 @@ </div> <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - Overlay opacity: @(_overlayOpacity/1000) - <input type="range" class="w-100" min="0" max="1000" @bind="_overlayOpacity" @oninput="(e => _overlayOpacity = int.Parse(e.Value?.ToString()))" /> + Overlay opacity: @(_overlayOpacity/100) + <input type="range" class="w-100" min="0" max="100" @bind="_overlayOpacity" @oninput="(e => _overlayOpacity = int.Parse(e.Value?.ToString()))" /> </div> </div> <div class="row pb-2"> @@ -38,7 +38,7 @@ </div> </div> - <LoadingPage IsLoading="@_pageIsLoading" OverlayBackgroundColor="@_overlayColor" OverlayOpacity="@(_overlayOpacity / 1000)"> + <LoadingPage IsLoading="@_pageIsLoading" OverlayBackgroundColor="@_overlayColor" OverlayOpacity="@(_overlayOpacity / 100)"> <LoadingContent> @((MarkupString)_overlayContent) </LoadingContent> @@ -60,8 +60,8 @@ </div> <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - Overlay opacity: @(_overlayOpacityTable / 1000) - <input type="range" class="w-100" min="0" max="1000" @bind="_overlayOpacityTable" @oninput="(e => _overlayOpacityTable = int.Parse(e.Value?.ToString()))" /> + Overlay opacity: @(_overlayOpacityTable / 100) + <input type="range" class="w-100" min="0" max="100" @bind="_overlayOpacityTable" @oninput="(e => _overlayOpacityTable = int.Parse(e.Value?.ToString()))" /> </div> </div> <div class="row pb-2"> @@ -70,7 +70,7 @@ </div> </div> - <LoadingElement IsLoading="@_elementIsLoading" OverlayBackgroundColor="@_overlayColorTable" OverlayOpacity="@(_overlayOpacityTable / 1000)"> + <LoadingElement IsLoading="@_elementIsLoading" OverlayBackgroundColor="@_overlayColorTable" OverlayOpacity="@(_overlayOpacityTable / 100)"> <LoadingContent> @((MarkupString)_overlayContent2) </LoadingContent> @@ -186,7 +186,7 @@ //Page Loading private string _overlayColor = "lightblue"; - private double _overlayOpacity = 500; + private double _overlayOpacity = 50; private string _overlayContent = @"<i class=""fa fa-cog fa-3x fa-spin""></i> <h2 class=""m-3"">Refreshing...</h2>"; @@ -208,7 +208,7 @@ //Element Loading private string _overlayColorTable = "orange"; - private double _overlayOpacityTable = 500; + private double _overlayOpacityTable = 50; private bool _elementIsLoading = false; private string _overlayContent2 = @"<i class=""fa fa-cog fa-3x fa-spin""></i> <h2 class=""m-3"">Refreshing...</h2>"; diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor index 7cbe7bc6..c79da20f 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Tabs.razor @@ -9,7 +9,7 @@ <div class="container-fluid p-3 mb-3 border rounded"> <h3>TabsPanel with TabItems</h3> - <p>Renders <strong><code>TabsPanel</code> container </strong> for <strong><code>TabItem</code> components</strong> with custumizable header and content.</p> + <p>Renders <strong><code>TabsPanel</code> container </strong> for <strong><code>TabItem</code> components</strong> with customizable header and content.</p> <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> From eabc0ba4cc6bc147168258c47f1fd6b7ae6fc245 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 12 Jul 2021 18:45:23 +0200 Subject: [PATCH 59/69] Make some components singleton. --- .../GdprBanner.razor | 104 +++++++++++------- .../PermaLinkBlazorServerInitializer.razor | 16 ++- .../PermalinkBlazorWasmInitializer.razor | 11 +- .../Components/GdprConsents.razor | 15 ++- .../Components/Permalink.razor | 4 +- 5 files changed, 97 insertions(+), 53 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor index d8246370..2becbcb2 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor @@ -1,50 +1,74 @@ <div class="banner" @ref="_inputRef" - style="background-color: rgba(@BannerBackgroundColor, @BannerOpacity.ToString("0.00", CultureInfo.InvariantCulture))" - @attributes=AllOtherAttributes> - @Content + style="background-color: rgba(@BannerBackgroundColor, @BannerOpacity.ToString("0.00", CultureInfo.InvariantCulture))" + @attributes=AllOtherAttributes> + @Content </div> @using System.Globalization @using Majorsoft.Blazor.Components.Core.HtmlColors + +@implements IDisposable + @inject ILogger<GdprBanner> _logger @inject IGdprConsentService _gdprConsentService @code { - private ElementReference _inputRef; - /// <summary> - /// Exposes a Blazor ElementReference of the wrapped around HTML element. It can be used e.g. for JS interop, etc. - /// </summary> - public ElementReference InnerElementReference => _inputRef; - - /// <summary> - /// HTML Content of the collapse panel. - /// </summary> - [Parameter] public RenderFragment Content { get; set; } - - private string _bannerColor = "128,128,128";//gray - /// <summary> - /// Sets the style of the HTML div background-color. Use HTML specified: Color Names, RGB, HEX or with HSL values. - /// </summary> - [Parameter] - public string BannerBackgroundColor - { - get => _bannerColor; - set => _bannerColor = new HtmlColor(value)?.RgbColor.ToRgbString(); - } - - /// <summary> - /// Opacity of the overlay div. Value should be between 0..1. Where 0 means the overlay layer is not visible. - /// </summary> - [Parameter] public double BannerOpacity { get; set; } = 0.9; - - /// <summary> - /// - /// </summary> - [Parameter] public DateTime AnswerValidUntil { get; set; } = DateTime.Now.AddMonths(1); - - /// <summary> - /// Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. - /// </summary> - [Parameter(CaptureUnmatchedValues = true)] - public Dictionary<string, object> AllOtherAttributes { get; set; } + private static bool Initialized = false; + protected override void OnInitialized() + { + if(Initialized) + { + throw new ApplicationException($"Component: '{nameof(GdprBanner)}' is not allowed to have multiple instances. Please define it one your e.g.: 'MainLayout.razor' or some common place."); + } + + Initialized = true; + } + + private ElementReference _inputRef; + /// <summary> + /// Exposes a Blazor ElementReference of the wrapped around HTML element. It can be used e.g. for JS interop, etc. + /// </summary> + public ElementReference InnerElementReference => _inputRef; + + /// <summary> + /// HTML Content of the collapse panel. + /// </summary> + [Parameter] public RenderFragment Content { get; set; } + + private string _bannerColor = "128,128,128";//gray + /// <summary> + /// Sets the style of the HTML div background-color. Use HTML specified: Color Names, RGB, HEX or with HSL values. + /// </summary> + [Parameter] + public string BannerBackgroundColor + { + get => _bannerColor; + set => _bannerColor = new HtmlColor(value)?.RgbColor.ToRgbString(); + } + + /// <summary> + /// Opacity of the overlay div. Value should be between 0..1. Where 0 means the overlay layer is not visible. + /// </summary> + [Parameter] public double BannerOpacity { get; set; } = 0.9; + + /// <summary> + /// + /// </summary> + [Parameter] public DateTime AnswerValidUntil { get; set; } = DateTime.Now.AddMonths(1); + + /// <summary> + /// Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. + /// </summary> + [Parameter(CaptureUnmatchedValues = true)] + public Dictionary<string, object> AllOtherAttributes { get; set; } + + public async void Dispose() + { + Initialized = false; + } + + private void WriteDiag(string message) + { + _logger.LogDebug($"Component {this.GetType()}: {message}"); + } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor index 9f46c100..916cf2e3 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor +++ b/src/Majorsoft.Blazor.Components.PermaLink/PermaLinkBlazorServerInitializer.razor @@ -5,11 +5,21 @@ @inject NavigationManager _navigationManager @inject ILogger<IPermaLinkWatcherService> _logger -@implements IDisposable @implements IAsyncDisposable @code { private IPermaLinkWatcherService _permalinkWatcher; + private static bool Initialized = false; + + protected override void OnInitialized() + { + if (Initialized) + { + throw new ApplicationException($"Component: '{nameof(PermalinkBlazorWasmInitializer)}' is not allowed to have multiple instances. Please define it one your e.g.: 'MainLayout.razor' or some common place."); + } + + Initialized = true; + } protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -27,10 +37,8 @@ { await _scrollHandler.DisposeAsync(); } - } - public void Dispose() - { _permalinkWatcher?.Dispose(); + Initialized = false; } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor b/src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor index f59b1e6f..8347ecbf 100644 --- a/src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor +++ b/src/Majorsoft.Blazor.Components.PermaLink/PermalinkBlazorWasmInitializer.razor @@ -2,13 +2,22 @@ @implements IDisposable @code { + private static bool Initialized = false; protected override void OnInitialized() { + if (Initialized) + { + throw new ApplicationException($"Component: '{nameof(PermalinkBlazorWasmInitializer)}' is not allowed to have multiple instances. Please define it one your e.g.: 'MainLayout.razor' or some common place."); + } + + Initialized = true; + _permalinkWatcher.WatchPermaLinks(); } public void Dispose() { - _permalinkWatcher.Dispose(); + _permalinkWatcher?.Dispose(); + Initialized = false; } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor index 9070cee4..0f434f0e 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor @@ -2,16 +2,17 @@ <h1>GDPR Consents</h1> <p> - Blazor component that renders a customizable GDPR consent Banner or Popup witch Accept/Reject for cookie settings chosen value is persisted to Browser storage. For usage see source code and docs on - <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/GdprConsent.md" target="_blank">Github</a>. + Blazor <strong>injectable <code>IGdprConsentService</code> service</strong> and components that renders a customizable GDPR consent Banner or Popup witch Accept/Reject for cookie settings chosen value is persisted to Browser storage. + To initialize GDPR Consents use <code>GdprBanner</code> or <code>GdprModal</code> <strong>only once</strong> in your Blazor App <code>MainLayout.razor</code> page or any common place. + For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/GdprConsent.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.GdprConsent</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.GdprConsent" target="_blank">Nuget</a> </p> <div class="container-fluid p-3 mb-3 border rounded"> <PermaLinkElement PermaLinkName="loading-page" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> - <Content><h3>Loading page</h3></Content> + <Content><h3>GDPR Consent Banner</h3></Content> </PermaLinkElement> - <p>Renders an Overlay layer for the whole page with customizable content for showing Page loading...</p> + <p>Renders a small Overlay layer at the bottom of the page with customizable content for showing the given GDPR message.</p> <div class="row pb-2"> @@ -46,8 +47,8 @@ <GdprBanner BannerOpacity="@(_bannerOpacity / 100)" BannerBackgroundColor="@_bannerColor"> <Content> <div class="gdpr-banner"> - <span class="fa fa-lg fa-cookie-bite" aria-hidden="true"></span> - This demo site actually does NOT uses cookies. Only demonstrate Cookie consent banner usage in your Blazor Application. + <span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> + <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent banner usage in your Blazor Application.</strong> <button type="button" class="btn btn-primary m-1">I agree</button> <button type="button" class="btn btn-secondary m-1">Disagree</button> @@ -55,6 +56,8 @@ </Content> </GdprBanner> +@inject IGdprConsentService _gdprConsentService; + @code { //GDPR banner private string _bannerColor = "lightblue"; diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor index 32cb67ce..6b8a71e9 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/Permalink.razor @@ -8,8 +8,8 @@ textarea { <h1>Permalink (#link) extension and component</h1> <p> - Blazor <strong>injectable <code>IPermaLinkWatcherService</code> service</strong> and <code>PermaLinkElement</code> wrapper component which allows navigation inside Blazor pages (#permalink). - To initialize navigation watcher use <code>PermalinkBlazorWasmInitializer</code> or <code>PermaLinkBlazorServerInitializer</code> in your Blazor app <code>MainLayout.razor</code> page. + Blazor <strong>injectable <code>IPermaLinkWatcherService</code> service</strong> and <code>PermaLinkElement</code> wrapper component which allows navigation inside Blazor pages (#permalink). + To initialize navigation watcher use <code>PermalinkBlazorWasmInitializer</code> or <code>PermaLinkBlazorServerInitializer</code> <strong>only once</strong> in your Blazor App <code>MainLayout.razor</code> page or any common place. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/PermaLink.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.PermaLink</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.PermaLink" target="_blank">Nuget</a> </p> From d9251404aaea369530fb3bc70c176ea1aa7ab157 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Mon, 12 Jul 2021 22:41:46 +0200 Subject: [PATCH 60/69] Implemented GdprBanner and IGdprConsentService --- .../ConsentNotificationEventHandler.cs | 10 ++ .../GdprBanner.razor | 84 ++++++++++- .../GdprConsentData.cs | 39 ++++++ .../GdprConsentDetail.cs | 18 +++ .../GdprConsentExtension.cs | 1 + .../GdprConsentService.cs | 37 +++++ .../IGdprConsentService.cs | 67 ++++++--- ...ajorsoft.Blazor.Components.GdprConsent.xml | 130 +++++++++++++++++- .../Components/GdprConsents.razor | 121 ++++++++++++---- 9 files changed, 452 insertions(+), 55 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/ConsentNotificationEventHandler.cs create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentData.cs create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentDetail.cs create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentService.cs diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/ConsentNotificationEventHandler.cs b/src/Majorsoft.Blazor.Components.GdprConsent/ConsentNotificationEventHandler.cs new file mode 100644 index 00000000..0486f843 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/ConsentNotificationEventHandler.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace Majorsoft.Blazor.Components.GdprConsent +{ + /// <summary> + /// Delegate for <see cref="IGdprConsentNotificationService"/> EventHandler. + /// </summary> + /// <returns>Task</returns> + public delegate Task ConsentNotificationEventHandler(); +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor index 2becbcb2..dbf4a45d 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor @@ -1,8 +1,11 @@ -<div class="banner" @ref="_inputRef" - style="background-color: rgba(@BannerBackgroundColor, @BannerOpacity.ToString("0.00", CultureInfo.InvariantCulture))" - @attributes=AllOtherAttributes> - @Content -</div> +@if (_shouldShow) +{ + <div class="banner" @ref="_inputRef" + style="background-color: rgba(@BannerBackgroundColor, @BannerOpacity.ToString("0.00", CultureInfo.InvariantCulture))" + @attributes=AllOtherAttributes> + @Content + </div> +} @using System.Globalization @using Majorsoft.Blazor.Components.Core.HtmlColors @@ -16,12 +19,21 @@ private static bool Initialized = false; protected override void OnInitialized() { - if(Initialized) + if (Initialized) { throw new ApplicationException($"Component: '{nameof(GdprBanner)}' is not allowed to have multiple instances. Please define it one your e.g.: 'MainLayout.razor' or some common place."); } Initialized = true; + _gdprConsentService.ConsentNotificationService.GdprConsentStateChanged += CheckConsent; + + WriteDiag("Initialized successfuly."); + } + + private bool _shouldShow = false; + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await CheckConsent(); } private ElementReference _inputRef; @@ -52,7 +64,13 @@ [Parameter] public double BannerOpacity { get; set; } = 0.9; /// <summary> - /// + /// Gets or Sets Consent name Banner should be used for simple usage to accept ALL cookies. Default: `Cookies.All` + /// For detailed choice use GdprModal. + /// </summary> + [Parameter] public string ConsentName { get; set; } = "Cookies.All"; + + /// <summary> + /// Gets or Sets Consent choice validity date. After this date Consent will be asked again. /// </summary> [Parameter] public DateTime AnswerValidUntil { get; set; } = DateTime.Now.AddMonths(1); @@ -62,9 +80,61 @@ [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> AllOtherAttributes { get; set; } + /// <summary> + /// Accepting GDPR Consents. + /// </summary> + /// <returns>ValueTask</returns> + public async ValueTask Accepted() + { + await _gdprConsentService.SetGdprConsentDataAsync(CreateGdprConsentData(true)); + WriteDiag("GDPR Consent was accepted."); + } + + /// <summary> + /// Rejecting GDPR Consents. + /// </summary> + /// <returns>ValueTask</returns> + public async ValueTask Rejected() + { + await _gdprConsentService.SetGdprConsentDataAsync(CreateGdprConsentData(false)); + WriteDiag("GDPR Consent was rejected."); + } + + private GdprConsentData CreateGdprConsentData(bool accepted) + { + return new GdprConsentData() + { + AnsweredAt = DateTime.Now, + AnswerValidUntil = AnswerValidUntil, + GdprConsentDetails = new List<GdprConsentDetail>() + { + new GdprConsentDetail() { IsAccepted = accepted, ConsentName = this.ConsentName } + } + }; + } + + private async Task CheckConsent() + { + var gdprConsent = await _gdprConsentService.GetGdprConsentDataAsync(); + var show = !gdprConsent?.IsValid ?? true; + + WriteDiag($"GDPR Consent value changed Banner should show: '{_shouldShow}'."); + + + if (show != _shouldShow) + { + _shouldShow = show; + StateHasChanged(); + } + } + + /// <summary> + /// Component dispose + /// </summary> public async void Dispose() { Initialized = false; + _gdprConsentService.ConsentNotificationService.GdprConsentStateChanged -= CheckConsent; } private void WriteDiag(string message) diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentData.cs b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentData.cs new file mode 100644 index 00000000..49a6ab3d --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentData.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; + +namespace Majorsoft.Blazor.Components.GdprConsent +{ + /// <summary> + /// GdprConsentData to store collections of <see cref="GdprConsentDetail"/> and validity date. + /// </summary> + public class GdprConsentData + { + /// <summary> + /// Collections of <see cref="GdprConsentDetail"/> + /// </summary> + public IEnumerable<GdprConsentDetail> GdprConsentDetails { get; set; } + + /// <summary> + /// Consent answered date. + /// </summary> + public DateTime AnsweredAt { get; set; } + + /// <summary> + /// Consent answer validity date. + /// </summary> + public DateTime AnswerValidUntil { get; set; } + + /// <summary> + /// Gets weather the Consent answer is valid or not. + /// </summary> + public bool IsValid => AnswerValidUntil >= DateTime.Now; + + /// <summary> + /// Default constructor + /// </summary> + public GdprConsentData() + { + GdprConsentDetails = new List<GdprConsentDetail>(); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentDetail.cs b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentDetail.cs new file mode 100644 index 00000000..ae9d7cc3 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentDetail.cs @@ -0,0 +1,18 @@ +namespace Majorsoft.Blazor.Components.GdprConsent +{ + /// <summary> + /// GdprConsentDetail with Name and acceptance indicator. + /// </summary> + public class GdprConsentDetail + { + /// <summary> + /// Name of the Consent type e.g: All, Session, Tracking. + /// </summary> + public string ConsentName { get; set; } = ""; + + /// <summary> + /// User accepted or rejected cookie consent. + /// </summary> + public bool IsAccepted { get; set; } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs index 086000cf..67fb8025 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs @@ -24,6 +24,7 @@ public static IServiceCollection AddGdprConsent(this IServiceCollection services services.AddBrowserStorage(); services.AddTransient<IGdprConsentService, GdprConsentService>(); + services.AddSingleton<IGdprConsentNotificationService, GdprConsentNotificationService>(); return services; } diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentService.cs b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentService.cs new file mode 100644 index 00000000..8841644f --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentService.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; + +using Majorsoft.Blazor.Extensions.BrowserStorage; + +namespace Majorsoft.Blazor.Components.GdprConsent +{ + /// <summary> + /// Implementation of <see cref="IGdprConsentService"/> + /// </summary> + public class GdprConsentService : IGdprConsentService + { + private readonly ILocalStorageService _storageService; + + public string ConsentStoreKeyName => "GDPR_ConsentData"; + + public IGdprConsentNotificationService ConsentNotificationService { get; } + + public GdprConsentService(ILocalStorageService storageService, + IGdprConsentNotificationService gdprConsentNotificationService) + { + _storageService = storageService; + ConsentNotificationService = gdprConsentNotificationService; + } + + public async ValueTask<GdprConsentData> GetGdprConsentDataAsync() => await _storageService.GetItemAsync<GdprConsentData>(ConsentStoreKeyName); + public async ValueTask SetGdprConsentDataAsync(GdprConsentData gdprConsentData) + { + await _storageService.SetItemAsync<GdprConsentData>(ConsentStoreKeyName, gdprConsentData); + ConsentNotificationService.OnChange(); + } + public async ValueTask ClearGdprConsentDataAsync() + { + await _storageService.RemoveItemAsync(ConsentStoreKeyName); + ConsentNotificationService.OnChange(); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs index 96034888..86c82664 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs +++ b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs @@ -1,39 +1,68 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Majorsoft.Blazor.Extensions.BrowserStorage; +using System.Threading.Tasks; namespace Majorsoft.Blazor.Components.GdprConsent { /// <summary> /// /// </summary> - public interface IGdprConsentService + public interface IGdprConsentNotificationService { - ValueTask<GdprConsentData> GetGdprConsentDataAsync(); + /// <summary> + /// + /// </summary> + event ConsentNotificationEventHandler GdprConsentStateChanged; + + /// <summary> + /// + /// </summary> + void OnChange(); } - public class GdprConsentService : IGdprConsentService + /// <summary> + /// + /// </summary> + public class GdprConsentNotificationService : IGdprConsentNotificationService { - private readonly ILocalStorageService _storageService; + public event ConsentNotificationEventHandler GdprConsentStateChanged; - public GdprConsentService(ILocalStorageService storageService) + public void OnChange() { - _storageService = storageService; + GdprConsentStateChanged?.Invoke(); } - - public ValueTask<GdprConsentData> GetGdprConsentDataAsync() => throw new NotImplementedException(); } - public class GdprConsentData + /// <summary> + /// Injectable service to handle GDPR Consent actions. + /// </summary> + public interface IGdprConsentService { - public bool IsAccepted { get; set; } + /// <summary> + /// Gets GDPR Consent Browser storage key name + /// </summary> + string ConsentStoreKeyName { get; } + + /// <summary> + /// + /// </summary> + IGdprConsentNotificationService ConsentNotificationService { get; } + + /// <summary> + /// + /// </summary> + /// <returns></returns> + ValueTask<GdprConsentData> GetGdprConsentDataAsync(); - public DateTime AnsweredAt { get; set; } + /// <summary> + /// + /// </summary> + /// <param name="gdprConsentData"></param> + /// <returns></returns> + ValueTask SetGdprConsentDataAsync(GdprConsentData gdprConsentData); - public DateTime AnswerValidUntil { get; set; } + /// <summary> + /// + /// </summary> + /// <returns></returns> + ValueTask ClearGdprConsentDataAsync(); } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml index 86285b61..afbbbd13 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml @@ -4,6 +4,57 @@ <name>Majorsoft.Blazor.Components.GdprConsent</name> </assembly> <members> + <member name="T:Majorsoft.Blazor.Components.GdprConsent.ConsentNotificationEventHandler"> + <summary> + Delegate for <see cref="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentNotificationService"/> EventHandler. + </summary> + <returns>Task</returns> + </member> + <member name="T:Majorsoft.Blazor.Components.GdprConsent.GdprConsentData"> + <summary> + GdprConsentData to store collections of <see cref="T:Majorsoft.Blazor.Components.GdprConsent.GdprConsentDetail"/> and validity date. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprConsentData.GdprConsentDetails"> + <summary> + Collections of <see cref="T:Majorsoft.Blazor.Components.GdprConsent.GdprConsentDetail"/> + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprConsentData.AnsweredAt"> + <summary> + Consent answered date. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprConsentData.AnswerValidUntil"> + <summary> + Consent answer validity date. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprConsentData.IsValid"> + <summary> + Gets weather the Consent answer is valid or not. + </summary> + </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprConsentData.#ctor"> + <summary> + Default constructor + </summary> + </member> + <member name="T:Majorsoft.Blazor.Components.GdprConsent.GdprConsentDetail"> + <summary> + GdprConsentDetail with Name and acceptance indicator. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprConsentDetail.ConsentName"> + <summary> + Name of the Consent type e.g: All, Session, Tracking. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprConsentDetail.IsAccepted"> + <summary> + User accepted or rejected cookie consent. + </summary> + </member> <member name="T:Majorsoft.Blazor.Components.GdprConsent.GdprConsentExtension"> <summary> Extension methods to register required JS Interop services into IServiceCollection @@ -15,10 +66,64 @@ </summary> <param name="services">IServiceCollection instance</param> </member> + <member name="T:Majorsoft.Blazor.Components.GdprConsent.GdprConsentService"> + <summary> + Implementation of <see cref="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService"/> + </summary> + </member> + <member name="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentNotificationService"> + <summary> + + </summary> + </member> + <member name="E:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentNotificationService.GdprConsentStateChanged"> + <summary> + + </summary> + </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentNotificationService.OnChange"> + <summary> + + </summary> + </member> + <member name="T:Majorsoft.Blazor.Components.GdprConsent.GdprConsentNotificationService"> + <summary> + + </summary> + </member> <member name="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService"> <summary> + Injectable service to handle GDPR Consent actions. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.ConsentStoreKeyName"> + <summary> + Gets GDPR Consent Browser storage key name + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.ConsentNotificationService"> + <summary> + + </summary> + </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.GetGdprConsentDataAsync"> + <summary> + + </summary> + <returns></returns> + </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.SetGdprConsentDataAsync(Majorsoft.Blazor.Components.GdprConsent.GdprConsentData)"> + <summary> + + </summary> + <param name="gdprConsentData"></param> + <returns></returns> + </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.ClearGdprConsentDataAsync"> + <summary> </summary> + <returns></returns> </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.InnerElementReference"> <summary> @@ -40,9 +145,15 @@ Opacity of the overlay div. Value should be between 0..1. Where 0 means the overlay layer is not visible. </summary> </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.ConsentName"> + <summary> + Gets or Sets Consent name Banner should be used for simple usage to accept ALL cookies. Default: `Cookies.All` + For detailed choice use GdprModal. + </summary> + </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.AnswerValidUntil"> <summary> - + Gets or Sets Consent choice validity date. After this date Consent will be asked again. </summary> </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.AllOtherAttributes"> @@ -50,5 +161,22 @@ Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. </summary> </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.Accepted"> + <summary> + Accepting GDPR Consents. + </summary> + <returns>ValueTask</returns> + </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.Rejected"> + <summary> + Rejecting GDPR Consents. + </summary> + <returns>ValueTask</returns> + </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.Dispose"> + <summary> + Component dispose + </summary> + </member> </members> </doc> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor index 0f434f0e..0c900010 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor @@ -14,56 +14,121 @@ </PermaLinkElement> <p>Renders a small Overlay layer at the bottom of the page with customizable content for showing the given GDPR message.</p> - <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> Overlay color (Name, RGB, Hex, HSL): <input class="form-control w-100" @bind="_bannerColor" /> </div> </div> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Consent valid: @(_bannerConsentValidDays) day(s) + <input type="range" class="w-100" min="1" max="1000" @bind="_bannerConsentValidDays" @oninput="(e => _bannerConsentValidDays = int.Parse(e.Value?.ToString()))" /> + </div> + </div> <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> Overlay opacity: @(_bannerOpacity / 100) <input type="range" class="w-100" min="0" max="100" @bind="_bannerOpacity" @oninput="(e => _bannerOpacity = int.Parse(e.Value?.ToString()))" /> </div> </div> - </div> -<style> - .gdpr-banner { - font-size: medium; - font-weight: 500; - padding: 16px 0; - display: flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - /*border: 1px solid #e0e0e0;*/ - } -</style> -<GdprBanner BannerOpacity="@(_bannerOpacity / 100)" BannerBackgroundColor="@_bannerColor"> - <Content> - <div class="gdpr-banner"> - <span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> - <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent banner usage in your Blazor Application.</strong> - - <button type="button" class="btn btn-primary m-1">I agree</button> - <button type="button" class="btn btn-secondary m-1">Disagree</button> +<div class="container-fluid p-3 mb-3 border rounded"> + <h3>GDPR Consent data</h3> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + @if (_gdprConsentData != null) + { + <label>Consent was answered at: <strong>@_gdprConsentData.AnsweredAt</strong> valid until: <strong>@_gdprConsentData.AnswerValidUntil</strong></label> + <table class="table table-striped"> + <tr> + <th>Consent Name</th> + <th>IsAccepted</th> + </tr> + @foreach (var item in _gdprConsentData.GdprConsentDetails) + { + <tr> + <td>@item.ConsentName</td> + <td>@item.IsAccepted</td> + </tr> + } + </table> + + <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprConsentService.ClearGdprConsentDataAsync()">Clear GDPR Consent</button> + } + else + { + <strong class="text-danger">GDPR Consents not yet accepted or rejected...</strong> + } </div> - </Content> -</GdprBanner> + </div> +</div> + +@*THIS should be in a common part of your App e.g.: MainLayout.razor !!!*@ +<div> + <style> + .gdpr-banner { + font-size: medium; + font-weight: 500; + padding: 16px 0; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + /*border: 1px solid #e0e0e0;*/ + } + </style> + <GdprBanner @ref="_gdprBanner" BannerOpacity="@(_bannerOpacity / 100)" BannerBackgroundColor="@_bannerColor" AnswerValidUntil="@DateTime.Now.AddDays(_bannerConsentValidDays)" ConsentName="All"> + <Content> + <div class="gdpr-banner"> + <span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> + <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent banner usage in your Blazor Application.</strong> + + <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprBanner.Accepted()">I agree</button> + <button type="button" class="btn btn-secondary m-1" @onclick="async () => await _gdprBanner.Rejected()">Disagree</button> + </div> + </Content> + </GdprBanner> +</div> + +@inject IGdprConsentService _gdprConsentService -@inject IGdprConsentService _gdprConsentService; +@implements IDisposable @code { + private GdprConsentData _gdprConsentData; + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + _gdprConsentService.ConsentNotificationService.GdprConsentStateChanged += OnConsentChanged; //Subscribe to change event + + await OnConsentChanged(); //Initial check + } + } + //GDPR banner + private GdprBanner _gdprBanner; private string _bannerColor = "lightblue"; + private int _bannerConsentValidDays = 20; private double _bannerOpacity = 90; //GDPR popup private string _overlayColor = "lightblue"; private double _overlayOpacity = 70; + + private async Task OnConsentChanged() + { + _gdprConsentData = await _gdprConsentService.GetGdprConsentDataAsync(); + StateHasChanged(); + } + + public async void Dispose() + { + _gdprConsentService.ConsentNotificationService.GdprConsentStateChanged -= OnConsentChanged; //Unsubscribe from change event + } } \ No newline at end of file From a925b2019197a77ef5e79c3698564021fdc4ba11 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Tue, 13 Jul 2021 21:33:13 +0200 Subject: [PATCH 61/69] Implemented GdprModal component. --- .../GdprModal.razor | 166 ++++++++++++++ ...ajorsoft.Blazor.Components.GdprConsent.xml | 78 +++++++ .../_Imports.razor | 4 +- .../Components/GdprConsents.razor | 208 +++++++++++++----- 4 files changed, 405 insertions(+), 51 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor b/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor new file mode 100644 index 00000000..1a892864 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor @@ -0,0 +1,166 @@ +<ModalDialog @ref="_modal" OverlayBackgroundColor="@OverlayBackgroundColor" OverlayOpacity="@OverlayOpacity" + Height="@Height" Width="@Width" MinHeight="@MinHeight" MinWidth="@MinWidth" Centered="@Centered" + CloseOnEscapeKey="false" + CloseOnOverlayClick="false" + ShowCloseButton="false" + Animate="false" + Focus="false" + @attributes=AllOtherAttributes> + <Content> + @Content + </Content> + <Footer> + @Footer + </Footer> +</ModalDialog> + +@implements IDisposable + +@inject ILogger<GdprModal> _logger +@inject IGdprConsentService _gdprConsentService + +@code { + private static bool Initialized = false; + protected override void OnInitialized() + { + if (Initialized) + { + throw new ApplicationException($"Component: '{nameof(GdprBanner)}' is not allowed to have multiple instances. Please define it one your e.g.: 'MainLayout.razor' or some common place."); + } + + Initialized = true; + _gdprConsentService.ConsentNotificationService.GdprConsentStateChanged += CheckConsent; + + WriteDiag("Initialized successfuly."); + } + + private ModalDialog _modal; + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await CheckConsent(); + } + + /// <summary> + /// HTML Content of the collapse panel. + /// </summary> + [Parameter] public RenderFragment Content { get; set; } + /// <summary> + /// HTML content to show on the Modal footer (bottom). Can be any valid HTML but should be only custom action buttons. + /// Must not be defined if you want to leave it out. + /// </summary> + [Parameter] public RenderFragment Footer { get; set; } + + /// <summary> + /// Sets the style of the HTML div background-color. Use HTML specified: Color Names, RGB, HEX or with HSL values. + /// </summary> + [Parameter] + public string OverlayBackgroundColor { get; set; } = "128,128,128";//gray + + /// <summary> + /// Opacity of the overlay div. Value should be between 0..1. Where 0 means the overlay layer is not visible. + /// </summary> + [Parameter] public double OverlayOpacity { get; set; } = 0.7; + + /// <summary> + /// + /// </summary> + [Parameter] public IEnumerable<GdprConsentDetail> ConsentDetails { get; set; } + + /// <summary> + /// Gets or Sets Consent choice validity date. After this date Consent will be asked again. + /// </summary> + [Parameter] public DateTime AnswerValidUntil { get; set; } = DateTime.Now.AddMonths(1); + + //Size + /// <summary> + /// Modal dialog window Height in px if set to 0 Height is set auto. + /// </summary> + [Parameter] public double Height { get; set; } = 0; + /// <summary> + /// Modal dialog window Width in px if set to 0 Width is set auto. + /// </summary> + [Parameter] public double Width { get; set; } = 0; + /// <summary> + /// Modal dialog window minimum Height in px. + /// </summary> + [Parameter] public double MinHeight { get; set; } = 200; + /// <summary> + /// Modal dialog window minimum Width in px. + /// </summary> + [Parameter] public double MinWidth { get; set; } = 200; + + /// <summary> + /// When true Modal dialog will be vertically centered, otherwise shown near to the top. Modal dialog horizontally always centered. + /// </summary> + [Parameter] public bool Centered { get; set; } = false; + + /// <summary> + /// Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. + /// </summary> + [Parameter(CaptureUnmatchedValues = true)] + public Dictionary<string, object> AllOtherAttributes { get; set; } + + + /// <summary> + /// Accepting GDPR Consents. + /// </summary> + /// <returns>ValueTask</returns> + public async ValueTask Accepted() + { + await _gdprConsentService.SetGdprConsentDataAsync(CreateGdprConsentData(true)); + WriteDiag("GDPR Consent was accepted."); + } + + /// <summary> + /// Rejecting GDPR Consents. + /// </summary> + /// <returns>ValueTask</returns> + public async ValueTask Rejected() + { + await _gdprConsentService.SetGdprConsentDataAsync(CreateGdprConsentData(false)); + WriteDiag("GDPR Consent was rejected."); + } + + private GdprConsentData CreateGdprConsentData(bool accepted) + { + return new GdprConsentData() + { + AnsweredAt = DateTime.Now, + AnswerValidUntil = AnswerValidUntil, + GdprConsentDetails = ConsentDetails, + }; + } + + private async Task CheckConsent() + { + var gdprConsent = await _gdprConsentService.GetGdprConsentDataAsync(); + var show = !gdprConsent?.IsValid ?? true; + + WriteDiag($"GDPR Consent value changed Banner should show: '{show}'."); + + if(show && !_modal.IsOpen) + { + await _modal.Open(); + StateHasChanged(); + } + else if (!show && _modal.IsOpen) + { + await _modal.Close(); + StateHasChanged(); + } + } + + /// <summary> + /// Component dispose + /// </summary> + public async void Dispose() + { + Initialized = false; + _gdprConsentService.ConsentNotificationService.GdprConsentStateChanged -= CheckConsent; + } + + private void WriteDiag(string message) + { + _logger.LogDebug($"Component {this.GetType()}: {message}"); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml index afbbbd13..2e278685 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml @@ -125,6 +125,84 @@ </summary> <returns></returns> </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.Content"> + <summary> + HTML Content of the collapse panel. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.Footer"> + <summary> + HTML content to show on the Modal footer (bottom). Can be any valid HTML but should be only custom action buttons. + Must not be defined if you want to leave it out. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.OverlayBackgroundColor"> + <summary> + Sets the style of the HTML div background-color. Use HTML specified: Color Names, RGB, HEX or with HSL values. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.OverlayOpacity"> + <summary> + Opacity of the overlay div. Value should be between 0..1. Where 0 means the overlay layer is not visible. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.ConsentDetails"> + <summary> + + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.AnswerValidUntil"> + <summary> + Gets or Sets Consent choice validity date. After this date Consent will be asked again. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.Height"> + <summary> + Modal dialog window Height in px if set to 0 Height is set auto. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.Width"> + <summary> + Modal dialog window Width in px if set to 0 Width is set auto. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.MinHeight"> + <summary> + Modal dialog window minimum Height in px. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.MinWidth"> + <summary> + Modal dialog window minimum Width in px. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.Centered"> + <summary> + When true Modal dialog will be vertically centered, otherwise shown near to the top. Modal dialog horizontally always centered. + </summary> + </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.AllOtherAttributes"> + <summary> + Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. + </summary> + </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprModal.Accepted"> + <summary> + Accepting GDPR Consents. + </summary> + <returns>ValueTask</returns> + </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprModal.Rejected"> + <summary> + Rejecting GDPR Consents. + </summary> + <returns>ValueTask</returns> + </member> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprModal.Dispose"> + <summary> + Component dispose + </summary> + </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.InnerElementReference"> <summary> Exposes a Blazor ElementReference of the wrapped around HTML element. It can be used e.g. for JS interop, etc. diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor b/src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor index 848647bc..e3b2772b 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor +++ b/src/Majorsoft.Blazor.Components.GdprConsent/_Imports.razor @@ -1,3 +1,5 @@ @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Web -@using Microsoft.Extensions.Logging; \ No newline at end of file +@using Microsoft.Extensions.Logging + +@using Majorsoft.Blazor.Components.Modal \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor index 0c900010..78e028b1 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor @@ -9,32 +9,156 @@ </p> <div class="container-fluid p-3 mb-3 border rounded"> - <PermaLinkElement PermaLinkName="loading-page" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> - <Content><h3>GDPR Consent Banner</h3></Content> - </PermaLinkElement> - <p>Renders a small Overlay layer at the bottom of the page with customizable content for showing the given GDPR message.</p> + <strong>GDPR Component type:</strong> (only one of the GDPR components should be used by apply it on a common place in your App.) + <select class="form-control selectpicker w-25" @bind="_gdprControlType"> + <option value="Banner">Banner</option> + <option value="Modal">Modal</option> + </select> +</div> - <div class="row pb-2"> - <div class="col-12 col-lg-8 col-xl-5"> - Overlay color (Name, RGB, Hex, HSL): <input class="form-control w-100" @bind="_bannerColor" /> +@if (_gdprControlType == "Banner") +{ + <div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="gdpr-banner" IconActions="PermaLinkIconActions.Copy | PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>GDPR Consent Banner</h3></Content> + </PermaLinkElement> + <p>Renders a small Overlay layer at the bottom of the page with customizable content for showing the given GDPR message.</p> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Banner color (Name, RGB, Hex, HSL): <input class="form-control w-100" @bind="_bannerColor" /> + </div> </div> - </div> - <div class="row pb-2"> - <div class="col-12 col-lg-8 col-xl-5"> - Consent valid: @(_bannerConsentValidDays) day(s) - <input type="range" class="w-100" min="1" max="1000" @bind="_bannerConsentValidDays" @oninput="(e => _bannerConsentValidDays = int.Parse(e.Value?.ToString()))" /> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Consent valid: @(_bannerConsentValidDays) day(s) + <input type="range" class="w-100" min="1" max="1000" @bind="_bannerConsentValidDays" @oninput="(e => _bannerConsentValidDays = int.Parse(e.Value?.ToString()))" /> + </div> + </div> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Banner opacity: @(_bannerOpacity / 100) + <input type="range" class="w-100" min="0" max="100" @bind="_bannerOpacity" @oninput="(e => _bannerOpacity = int.Parse(e.Value?.ToString()))" /> + </div> </div> </div> - <div class="row pb-2"> - <div class="col-12 col-lg-8 col-xl-5"> - Overlay opacity: @(_bannerOpacity / 100) - <input type="range" class="w-100" min="0" max="100" @bind="_bannerOpacity" @oninput="(e => _bannerOpacity = int.Parse(e.Value?.ToString()))" /> + + @*THIS should be in a common part of your App e.g.: MainLayout.razor !!!*@ + <div> + <style> + .gdpr-banner { + font-size: medium; + font-weight: 500; + padding: 16px 0; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + /*border: 1px solid #e0e0e0;*/ + } + </style> + <GdprBanner @ref="_gdprBanner" + BannerOpacity="@(_bannerOpacity / 100)" + BannerBackgroundColor="@_bannerColor" + AnswerValidUntil="@DateTime.Now.AddDays(_bannerConsentValidDays)" + ConsentName="All"> + <Content> + <div class="gdpr-banner"> + <span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> + <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent Banner usage in your Blazor Application.</strong> + + <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprBanner.Accepted()">I agree</button> + <button type="button" class="btn btn-secondary m-1" @onclick="async () => await _gdprBanner.Rejected()">Disagree</button> + </div> + </Content> + </GdprBanner> + </div> +} +else if (_gdprControlType == "Modal") +{ + <div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="gdpr-modal" IconActions="PermaLinkIconActions.Copy | PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>GDPR Consent Modal</h3></Content> + </PermaLinkElement> + <p>Renders a Modal dialog with Overlay layer for the whole page with customizable content for showing the given GDPR message.</p> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Overlay color (Name, RGB, Hex, HSL): <input class="form-control w-100" @bind="_overlayColor" /> + </div> + </div> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Consent valid: @(_bannerConsentValidDays) day(s) + <input type="range" class="w-100" min="1" max="1000" @bind="_bannerConsentValidDays" @oninput="(e => _bannerConsentValidDays = int.Parse(e.Value?.ToString()))" /> + </div> + </div> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Overlay opacity: @(_overlayOpacity / 100) + <input type="range" class="w-100" min="0" max="100" @bind="_overlayOpacity" @oninput="(e => _overlayOpacity = int.Parse(e.Value?.ToString()))" /> + </div> </div> </div> -</div> + + <GdprModal @ref="_gdprModal" + OverlayBackgroundColor="@_overlayColor" + OverlayOpacity="@(_overlayOpacity /100)" + ConsentDetails="@_gdprConsents" + Centered="true"> + <Content> + <div class="container-fluid p-3 mb-3"> + <div class="row"> + <div class="col-12"> + <h2 class="" style="justify-content: center;"><span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> Cookie Consent</h2> + </div> + </div> + <div class="row mt-3 mb-4"> + <div class="col-10"> + <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent Modal usage in your Blazor Application.</strong> + </div> + </div> + <div class="row mb-2"> + <div class="col-8"> + Accept all Cookies: + </div> + <div class="col-4"> + <ToggleSwitch @bind-Checked="_gdprConsents[0].IsAccepted" Width="60" Height="25" /> + </div> + <hr /> + </div> + <div class="row mb-1"> + <div class="col-8"> + Session all Cookies: + </div> + <div class="col-4"> + <ToggleSwitch @bind-Checked="_gdprConsents[1].IsAccepted" Disabled="_gdprConsents[0].IsAccepted" Width="60" Height="25" /> + </div> + </div> + <div class="row mb-1"> + <div class="col-8"> + Tracking all Cookies: + </div> + <div class="col-4"> + <ToggleSwitch @bind-Checked="_gdprConsents[2].IsAccepted" Disabled="@_gdprConsents[0].IsAccepted" Width="60" Height="25" /> + </div> + </div> + </div> + </Content> + <Footer> + <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprModal.Accepted()">I agree</button> + <button type="button" class="btn btn-secondary m-1" @onclick="async () => await _gdprModal.Rejected()">Disagree</button> + </Footer> + </GdprModal> +} <div class="container-fluid p-3 mb-3 border rounded"> - <h3>GDPR Consent data</h3> + <PermaLinkElement PermaLinkName="gdpr-consent" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>GDPR Consent data</h3></Content> + </PermaLinkElement> <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> @@ -43,7 +167,7 @@ <label>Consent was answered at: <strong>@_gdprConsentData.AnsweredAt</strong> valid until: <strong>@_gdprConsentData.AnswerValidUntil</strong></label> <table class="table table-striped"> <tr> - <th>Consent Name</th> + <th>Cookie Consent Name</th> <th>IsAccepted</th> </tr> @foreach (var item in _gdprConsentData.GdprConsentDetails) @@ -65,36 +189,6 @@ </div> </div> -@*THIS should be in a common part of your App e.g.: MainLayout.razor !!!*@ -<div> - <style> - .gdpr-banner { - font-size: medium; - font-weight: 500; - padding: 16px 0; - display: flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - /*border: 1px solid #e0e0e0;*/ - } - </style> - <GdprBanner @ref="_gdprBanner" BannerOpacity="@(_bannerOpacity / 100)" BannerBackgroundColor="@_bannerColor" AnswerValidUntil="@DateTime.Now.AddDays(_bannerConsentValidDays)" ConsentName="All"> - <Content> - <div class="gdpr-banner"> - <span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> - <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent banner usage in your Blazor Application.</strong> - - <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprBanner.Accepted()">I agree</button> - <button type="button" class="btn btn-secondary m-1" @onclick="async () => await _gdprBanner.Rejected()">Disagree</button> - </div> - </Content> - </GdprBanner> -</div> - @inject IGdprConsentService _gdprConsentService @implements IDisposable @@ -111,6 +205,19 @@ } } + private string _gdprControlType = "Banner"; + private List<GdprConsentDetail> _gdprConsents; + + protected override void OnInitialized() + { + _gdprConsents = new List<GdprConsentDetail>() + { + new GdprConsentDetail() { ConsentName = "All", IsAccepted = true }, + new GdprConsentDetail() { ConsentName = "Session", IsAccepted = true }, + new GdprConsentDetail() { ConsentName = "Tracking", IsAccepted = true }, + }; + } + //GDPR banner private GdprBanner _gdprBanner; private string _bannerColor = "lightblue"; @@ -118,7 +225,8 @@ private double _bannerOpacity = 90; //GDPR popup - private string _overlayColor = "lightblue"; + private GdprModal _gdprModal; + private string _overlayColor = "lightgray"; private double _overlayOpacity = 70; private async Task OnConsentChanged() From 62e5b09a813f90c8fd4028367fc799dccc95ba50 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Wed, 14 Jul 2021 17:31:41 +0200 Subject: [PATCH 62/69] GDPR consent fixes and demo --- .../GdprBanner.razor | 12 +++-- .../GdprConsentExtension.cs | 5 +++ .../GdprModal.razor | 45 +++++++++---------- ...rsoft.Blazor.Components.GdprConsent.csproj | 2 + ...ajorsoft.Blazor.Components.GdprConsent.xml | 17 +++---- .../Components/GdprConsents.razor | 43 +++++++++++------- 6 files changed, 65 insertions(+), 59 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor index dbf4a45d..1a113962 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor @@ -64,10 +64,9 @@ [Parameter] public double BannerOpacity { get; set; } = 0.9; /// <summary> - /// Gets or Sets Consent name Banner should be used for simple usage to accept ALL cookies. Default: `Cookies.All` - /// For detailed choice use GdprModal. + /// GDPR Cookies consent details. An enumerable list of Cookie types with Accpeted flag. /// </summary> - [Parameter] public string ConsentName { get; set; } = "Cookies.All"; + [Parameter] public IEnumerable<GdprConsentDetail> ConsentDetails { get; set; } = new GdprConsentDetail[] { new GdprConsentDetail() { ConsentName = "Cookies.All" } }; /// <summary> /// Gets or Sets Consent choice validity date. After this date Consent will be asked again. @@ -102,14 +101,13 @@ private GdprConsentData CreateGdprConsentData(bool accepted) { + ConsentDetails?.ToList().ForEach(f => f.IsAccepted = accepted); return new GdprConsentData() { AnsweredAt = DateTime.Now, AnswerValidUntil = AnswerValidUntil, - GdprConsentDetails = new List<GdprConsentDetail>() - { - new GdprConsentDetail() { IsAccepted = accepted, ConsentName = this.ConsentName } - } + GdprConsentDetails = ConsentDetails + }; } diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs index 67fb8025..23a9364a 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentExtension.cs @@ -1,5 +1,7 @@ using System; +using Majorsoft.Blazor.Components.Common.JsInterop; +using Majorsoft.Blazor.Components.CssEvents; using Majorsoft.Blazor.Extensions.BrowserStorage; using Microsoft.Extensions.DependencyInjection; @@ -23,6 +25,9 @@ public static IServiceCollection AddGdprConsent(this IServiceCollection services } services.AddBrowserStorage(); + services.AddJsInteropExtensions(); + services.AddCssEvents(); + services.AddTransient<IGdprConsentService, GdprConsentService>(); services.AddSingleton<IGdprConsentNotificationService, GdprConsentNotificationService>(); diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor b/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor index 1a892864..c5295568 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor @@ -1,11 +1,16 @@ -<ModalDialog @ref="_modal" OverlayBackgroundColor="@OverlayBackgroundColor" OverlayOpacity="@OverlayOpacity" - Height="@Height" Width="@Width" MinHeight="@MinHeight" MinWidth="@MinWidth" Centered="@Centered" - CloseOnEscapeKey="false" - CloseOnOverlayClick="false" - ShowCloseButton="false" - Animate="false" - Focus="false" - @attributes=AllOtherAttributes> +<ModalDialog @ref="_modal" + OverlayBackgroundColor="@OverlayBackgroundColor" + OverlayOpacity="@OverlayOpacity" + Height="@Height" + Width="@Width" + MinHeight="@MinHeight" + MinWidth="@MinWidth" + Centered="@Centered" + CloseOnEscapeKey="false" + CloseOnOverlayClick="false" + ShowCloseButton="false" + Animate="false" + AllOtherAttributes="AllOtherAttributes"> <Content> @Content </Content> @@ -25,7 +30,7 @@ { if (Initialized) { - throw new ApplicationException($"Component: '{nameof(GdprBanner)}' is not allowed to have multiple instances. Please define it one your e.g.: 'MainLayout.razor' or some common place."); + throw new ApplicationException($"Component: '{nameof(GdprModal)}' is not allowed to have multiple instances. Please define it one your e.g.: 'MainLayout.razor' or some common place."); } Initialized = true; @@ -62,9 +67,9 @@ [Parameter] public double OverlayOpacity { get; set; } = 0.7; /// <summary> - /// + /// GDPR Cookies consent details. An enumerable list of Cookie types with Accpeted flag. /// </summary> - [Parameter] public IEnumerable<GdprConsentDetail> ConsentDetails { get; set; } + [Parameter] public IEnumerable<GdprConsentDetail> ConsentDetails { get; set; } = new GdprConsentDetail[] { new GdprConsentDetail() { ConsentName = "Cookies.All" } }; /// <summary> /// Gets or Sets Consent choice validity date. After this date Consent will be asked again. @@ -102,26 +107,16 @@ /// <summary> - /// Accepting GDPR Consents. + /// Save choiced GDPR Consents. /// </summary> /// <returns>ValueTask</returns> - public async ValueTask Accepted() + public async ValueTask SaveChoice() { - await _gdprConsentService.SetGdprConsentDataAsync(CreateGdprConsentData(true)); + await _gdprConsentService.SetGdprConsentDataAsync(CreateGdprConsentData()); WriteDiag("GDPR Consent was accepted."); } - /// <summary> - /// Rejecting GDPR Consents. - /// </summary> - /// <returns>ValueTask</returns> - public async ValueTask Rejected() - { - await _gdprConsentService.SetGdprConsentDataAsync(CreateGdprConsentData(false)); - WriteDiag("GDPR Consent was rejected."); - } - - private GdprConsentData CreateGdprConsentData(bool accepted) + private GdprConsentData CreateGdprConsentData() { return new GdprConsentData() { diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj index b3c8dff0..dc3ce3a6 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj @@ -41,6 +41,8 @@ </ItemGroup> <ItemGroup> + <ProjectReference Include="..\Majorsoft.Blazor.Components.Common.JsInterop\Majorsoft.Blazor.Components.Common.JsInterop.csproj" /> + <ProjectReference Include="..\Majorsoft.Blazor.Components.CssEvents\Majorsoft.Blazor.Components.CssEvents.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Modal\Majorsoft.Blazor.Components.Modal.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Components.Toggle\Majorsoft.Blazor.Components.Toggle.csproj" /> <ProjectReference Include="..\Majorsoft.Blazor.Extensions.BrowserStorage\Majorsoft.Blazor.Extensions.BrowserStorage.csproj" /> diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml index 2e278685..0d53aec9 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml @@ -148,7 +148,7 @@ </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.ConsentDetails"> <summary> - + GDPR Cookies consent details. An enumerable list of Cookie types with Accpeted flag. </summary> </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.AnswerValidUntil"> @@ -186,15 +186,9 @@ Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. </summary> </member> - <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprModal.Accepted"> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprModal.SaveChoice"> <summary> - Accepting GDPR Consents. - </summary> - <returns>ValueTask</returns> - </member> - <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprModal.Rejected"> - <summary> - Rejecting GDPR Consents. + Save choiced GDPR Consents. </summary> <returns>ValueTask</returns> </member> @@ -223,10 +217,9 @@ Opacity of the overlay div. Value should be between 0..1. Where 0 means the overlay layer is not visible. </summary> </member> - <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.ConsentName"> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.ConsentDetails"> <summary> - Gets or Sets Consent name Banner should be used for simple usage to accept ALL cookies. Default: `Cookies.All` - For detailed choice use GdprModal. + GDPR Cookies consent details. An enumerable list of Cookie types with Accpeted flag. </summary> </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.AnswerValidUntil"> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor index 78e028b1..585d4949 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor @@ -64,7 +64,7 @@ BannerOpacity="@(_bannerOpacity / 100)" BannerBackgroundColor="@_bannerColor" AnswerValidUntil="@DateTime.Now.AddDays(_bannerConsentValidDays)" - ConsentName="All"> + ConsentDetails="@_gdprConsents"> <Content> <div class="gdpr-banner"> <span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> @@ -72,6 +72,8 @@ <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprBanner.Accepted()">I agree</button> <button type="button" class="btn btn-secondary m-1" @onclick="async () => await _gdprBanner.Rejected()">Disagree</button> + + <button type="button" class="btn btn-warning m-1" @onclick='() => { _gdprControlType = "Modal"; }'>Customize</button> </div> </Content> </GdprBanner> @@ -104,6 +106,7 @@ else if (_gdprControlType == "Modal") </div> </div> + @*THIS should be in a common part of your App e.g.: MainLayout.razor !!!*@ <GdprModal @ref="_gdprModal" OverlayBackgroundColor="@_overlayColor" OverlayOpacity="@(_overlayOpacity /100)" @@ -117,40 +120,41 @@ else if (_gdprControlType == "Modal") </div> </div> <div class="row mt-3 mb-4"> - <div class="col-10"> + <div class="col-12"> <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent Modal usage in your Blazor Application.</strong> </div> </div> <div class="row mb-2"> - <div class="col-8"> - Accept all Cookies: + <div class="col-10"> + Required Cookies: </div> - <div class="col-4"> - <ToggleSwitch @bind-Checked="_gdprConsents[0].IsAccepted" Width="60" Height="25" /> + <div class="col-2"> + <ToggleSwitch Checked="_gdprConsents[0].IsAccepted" Disabled="true" Width="60" Height="25" /> </div> <hr /> </div> <div class="row mb-1"> - <div class="col-8"> + <div class="col-10"> Session all Cookies: </div> - <div class="col-4"> - <ToggleSwitch @bind-Checked="_gdprConsents[1].IsAccepted" Disabled="_gdprConsents[0].IsAccepted" Width="60" Height="25" /> + <div class="col-2"> + <ToggleSwitch @bind-Checked="_gdprConsents[1].IsAccepted" @bind-Checked:event="OnToggleChanged" Width="60" Height="25" /> </div> </div> <div class="row mb-1"> - <div class="col-8"> + <div class="col-10"> Tracking all Cookies: </div> - <div class="col-4"> - <ToggleSwitch @bind-Checked="_gdprConsents[2].IsAccepted" Disabled="@_gdprConsents[0].IsAccepted" Width="60" Height="25" /> + <div class="col-2"> + <ToggleSwitch @bind-Checked="_gdprConsents[2].IsAccepted" @bind-Checked:event="OnToggleChanged" Width="60" Height="25" /> </div> </div> </div> </Content> <Footer> - <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprModal.Accepted()">I agree</button> - <button type="button" class="btn btn-secondary m-1" @onclick="async () => await _gdprModal.Rejected()">Disagree</button> + <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprModal.SaveChoice()">Confirm my choice</button> + <button type="button" class="btn btn-secondary m-1" + @onclick="async () => { _gdprConsents.ForEach(f => f.IsAccepted = true); await _gdprModal.SaveChoice(); }">Accept all</button> </Footer> </GdprModal> } @@ -165,6 +169,15 @@ else if (_gdprControlType == "Modal") @if (_gdprConsentData != null) { <label>Consent was answered at: <strong>@_gdprConsentData.AnsweredAt</strong> valid until: <strong>@_gdprConsentData.AnswerValidUntil</strong></label> + if (_gdprConsentData.IsValid) + { + <label>Consent is: <strong>Valid</strong></label> + } + else + { + <label class="text-danger">Consent is: <strong>Invalid</strong></label> + } + <table class="table table-striped"> <tr> <th>Cookie Consent Name</th> @@ -212,7 +225,7 @@ else if (_gdprControlType == "Modal") { _gdprConsents = new List<GdprConsentDetail>() { - new GdprConsentDetail() { ConsentName = "All", IsAccepted = true }, + new GdprConsentDetail() { ConsentName = "Required", IsAccepted = true }, new GdprConsentDetail() { ConsentName = "Session", IsAccepted = true }, new GdprConsentDetail() { ConsentName = "Tracking", IsAccepted = true }, }; From 4b6f097193977c5789ed15a94e7521bf9827071a Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Wed, 14 Jul 2021 17:32:32 +0200 Subject: [PATCH 63/69] Modal dialog fixes and unit tests updates. --- .../ModalDialogTest.cs | 30 +++++++++---------- .../ModalDialog.razor | 2 ++ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Majorsoft.Blazor.Components.Modal.Tests/ModalDialogTest.cs b/src/Majorsoft.Blazor.Components.Modal.Tests/ModalDialogTest.cs index e5b14c5a..8e485e2a 100644 --- a/src/Majorsoft.Blazor.Components.Modal.Tests/ModalDialogTest.cs +++ b/src/Majorsoft.Blazor.Components.Modal.Tests/ModalDialogTest.cs @@ -63,7 +63,7 @@ public async Task ModalDialog_should_rendered_correctly_html_attributes_when_ope ); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -107,7 +107,7 @@ public async Task ModalDialog_should_remove_dialog_from_DOM_when_closed() rendered.MarkupMatches(""); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); Assert.AreEqual(true, rendered.Instance.IsOpen); @@ -142,7 +142,7 @@ public async Task ModalDialog_should_remove_dialog_from_DOM_when_closed() rendered.SetParametersAndRender(parameters => parameters .Add(p => p.Animate, false)); - rendered.InvokeAsync(async () => await rendered.Instance.Close()); + await rendered.InvokeAsync(async () => await rendered.Instance.Close()); rendered.Render(); Assert.AreEqual(false, rendered.Instance.IsOpen); @@ -157,7 +157,7 @@ public async Task ModalDialog_should_rendered_background_correctly() .Add(p => p.OverlayOpacity, 0.25)); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -201,7 +201,7 @@ public async Task ModalDialog_should_rendered_dimensions_correctly() .Add(p => p.MinWidth, 555)); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -242,7 +242,7 @@ public async Task ModalDialog_should_not_rendered_close_button_correctly() .Add(p => p.ShowCloseButton, false)); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -277,7 +277,7 @@ public async Task ModalDialog_should_rendered_dialog_centered_correctly() .Add(p => p.Centered, true)); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -323,7 +323,7 @@ public async Task ModalDialog_should_not_rendered_close_button_but_render_haeder ); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -364,7 +364,7 @@ public async Task ModalDialog_should_rendered_close_button_with_haeder_correctly ); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -408,7 +408,7 @@ public async Task ModalDialog_should_rendered_content_correctly() ); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -448,7 +448,7 @@ public async Task ModalDialog_should_rendered_footer_correctly() ); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -486,7 +486,7 @@ public async Task ModalDialog_should_close_on_overlay_click() .Add(p => p.OnClose, args => { closed = true; })); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -519,7 +519,7 @@ public async Task ModalDialog_should_not_close_on_overlay_click() .Add(p => p.OnClose, args => { closed = true; })); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -569,7 +569,7 @@ public async Task ModalDialog_should_close_on_escape_key() .Add(p => p.OnClose, args => { closed = true; })); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); @@ -602,7 +602,7 @@ public async Task ModalDialog_should_not_close_on_escape_key() .Add(p => p.OnClose, args => { closed = true; })); //Open - await rendered.Instance.Open(); + await rendered.InvokeAsync(async () => { await rendered.Instance.Open(); }); rendered.Render(); var div = rendered.Find("div"); diff --git a/src/Majorsoft.Blazor.Components.Modal/ModalDialog.razor b/src/Majorsoft.Blazor.Components.Modal/ModalDialog.razor index 4251654d..37a3b70d 100644 --- a/src/Majorsoft.Blazor.Components.Modal/ModalDialog.razor +++ b/src/Majorsoft.Blazor.Components.Modal/ModalDialog.razor @@ -251,7 +251,9 @@ { _dialogTop = _dialogDefaultTop;//Reset dialog to page top _isOpened = true; + WriteDiag($"Opening dialog currently, Opacity: {_opacity}, IsOpened: '{_isOpened}'."); + StateHasChanged(); //Force UI to render if (Animate) { From e7a512c4ff411d1cb4f5ddc9408df542c1911c28 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Wed, 14 Jul 2021 18:32:38 +0200 Subject: [PATCH 64/69] Added GDPR components XML comments. --- .../GdprBanner.razor | 4 +- .../GdprConsentNotificationService.cs | 15 +++++++ .../GdprModal.razor | 4 +- .../IGdprConsentNotificationService.cs | 18 ++++++++ .../IGdprConsentService.cs | 45 ++++--------------- ...ajorsoft.Blazor.Components.GdprConsent.xml | 38 ++++++++-------- .../Components/GdprConsents.razor | 7 +-- 7 files changed, 68 insertions(+), 63 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentNotificationService.cs create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentNotificationService.cs diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor index 1a113962..1455dea9 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor @@ -80,7 +80,7 @@ public Dictionary<string, object> AllOtherAttributes { get; set; } /// <summary> - /// Accepting GDPR Consents. + /// Accepting all GDPR Consents. /// </summary> /// <returns>ValueTask</returns> public async ValueTask Accepted() @@ -90,7 +90,7 @@ } /// <summary> - /// Rejecting GDPR Consents. + /// Rejecting all GDPR Consents. /// </summary> /// <returns>ValueTask</returns> public async ValueTask Rejected() diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentNotificationService.cs b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentNotificationService.cs new file mode 100644 index 00000000..a77898be --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentNotificationService.cs @@ -0,0 +1,15 @@ +namespace Majorsoft.Blazor.Components.GdprConsent +{ + /// <summary> + /// Default implementation for <see cref="IGdprConsentNotificationService"/> + /// </summary> + public class GdprConsentNotificationService : IGdprConsentNotificationService + { + public event ConsentNotificationEventHandler GdprConsentStateChanged; + + public void OnChange() + { + GdprConsentStateChanged?.Invoke(); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor b/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor index c5295568..38d7daa4 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor @@ -107,7 +107,7 @@ /// <summary> - /// Save choiced GDPR Consents. + /// Save user choosen GDPR Consents. /// </summary> /// <returns>ValueTask</returns> public async ValueTask SaveChoice() @@ -148,7 +148,7 @@ /// <summary> /// Component dispose /// </summary> - public async void Dispose() + public void Dispose() { Initialized = false; _gdprConsentService.ConsentNotificationService.GdprConsentStateChanged -= CheckConsent; diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentNotificationService.cs b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentNotificationService.cs new file mode 100644 index 00000000..b4944d7d --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentNotificationService.cs @@ -0,0 +1,18 @@ +namespace Majorsoft.Blazor.Components.GdprConsent +{ + /// <summary> + /// Injectable singleton service to handle GDPR Consent changes. + /// </summary> + public interface IGdprConsentNotificationService + { + /// <summary> + /// Event handler to subscribe for GDPR Consent changes. + /// </summary> + event ConsentNotificationEventHandler GdprConsentStateChanged; + + /// <summary> + /// Method called by <see cref="IGdprConsentService"/> to trigger Change event when GDPR Consent value was changed. + /// </summary> + void OnChange(); + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs index 86c82664..70cc68ad 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs +++ b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs @@ -2,35 +2,6 @@ namespace Majorsoft.Blazor.Components.GdprConsent { - /// <summary> - /// - /// </summary> - public interface IGdprConsentNotificationService - { - /// <summary> - /// - /// </summary> - event ConsentNotificationEventHandler GdprConsentStateChanged; - - /// <summary> - /// - /// </summary> - void OnChange(); - } - - /// <summary> - /// - /// </summary> - public class GdprConsentNotificationService : IGdprConsentNotificationService - { - public event ConsentNotificationEventHandler GdprConsentStateChanged; - - public void OnChange() - { - GdprConsentStateChanged?.Invoke(); - } - } - /// <summary> /// Injectable service to handle GDPR Consent actions. /// </summary> @@ -42,27 +13,27 @@ public interface IGdprConsentService string ConsentStoreKeyName { get; } /// <summary> - /// + /// Gets a <see cref="IGdprConsentNotificationService"/> to able to subscribe on Consent changed events. /// </summary> IGdprConsentNotificationService ConsentNotificationService { get; } /// <summary> - /// + /// Gets the GDPR Consent data from Browser local storage if any stored. /// </summary> - /// <returns></returns> + /// <returns>ValueTask</returns> ValueTask<GdprConsentData> GetGdprConsentDataAsync(); /// <summary> - /// + /// Sets or overrides the given GDPR Consent data in Browser local storage. /// </summary> - /// <param name="gdprConsentData"></param> - /// <returns></returns> + /// <param name="gdprConsentData">GDPR consent details with accepted and rejected cookies list</param> + /// <returns>ValueTask</returns> ValueTask SetGdprConsentDataAsync(GdprConsentData gdprConsentData); /// <summary> - /// + /// Removes the GDPR Consent data from Browser local storage if any stored. /// </summary> - /// <returns></returns> + /// <returns>ValueTask</returns> ValueTask ClearGdprConsentDataAsync(); } } \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml index 0d53aec9..e54aa8cf 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml @@ -66,6 +66,11 @@ </summary> <param name="services">IServiceCollection instance</param> </member> + <member name="T:Majorsoft.Blazor.Components.GdprConsent.GdprConsentNotificationService"> + <summary> + Default implementation for <see cref="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentNotificationService"/> + </summary> + </member> <member name="T:Majorsoft.Blazor.Components.GdprConsent.GdprConsentService"> <summary> Implementation of <see cref="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService"/> @@ -73,22 +78,17 @@ </member> <member name="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentNotificationService"> <summary> - + Injectable singleton service to handle GDPR Consent changes. </summary> </member> <member name="E:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentNotificationService.GdprConsentStateChanged"> <summary> - + Event handler to subscribe for GDPR Consent changes. </summary> </member> <member name="M:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentNotificationService.OnChange"> <summary> - - </summary> - </member> - <member name="T:Majorsoft.Blazor.Components.GdprConsent.GdprConsentNotificationService"> - <summary> - + Method called by <see cref="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService"/> to trigger Change event when GDPR Consent value was changed. </summary> </member> <member name="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService"> @@ -103,27 +103,27 @@ </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.ConsentNotificationService"> <summary> - + Gets a <see cref="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentNotificationService"/> to able to subscribe on Consent changed events. </summary> </member> <member name="M:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.GetGdprConsentDataAsync"> <summary> - + Gets the GDPR Consent data from Browser local storage if any stored. </summary> - <returns></returns> + <returns>ValueTask</returns> </member> <member name="M:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.SetGdprConsentDataAsync(Majorsoft.Blazor.Components.GdprConsent.GdprConsentData)"> <summary> - + Sets or overrides the given GDPR Consent data in Browser local storage. </summary> - <param name="gdprConsentData"></param> - <returns></returns> + <param name="gdprConsentData">GDPR consent details with accepted and rejected cookies list</param> + <returns>ValueTask</returns> </member> <member name="M:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.ClearGdprConsentDataAsync"> <summary> - + Removes the GDPR Consent data from Browser local storage if any stored. </summary> - <returns></returns> + <returns>ValueTask</returns> </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.Content"> <summary> @@ -188,7 +188,7 @@ </member> <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprModal.SaveChoice"> <summary> - Save choiced GDPR Consents. + Save user choosen GDPR Consents. </summary> <returns>ValueTask</returns> </member> @@ -234,13 +234,13 @@ </member> <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.Accepted"> <summary> - Accepting GDPR Consents. + Accepting all GDPR Consents. </summary> <returns>ValueTask</returns> </member> <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.Rejected"> <summary> - Rejecting GDPR Consents. + Rejecting all GDPR Consents. </summary> <returns>ValueTask</returns> </member> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor index 585d4949..040e6714 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor @@ -9,7 +9,8 @@ </p> <div class="container-fluid p-3 mb-3 border rounded"> - <strong>GDPR Component type:</strong> (only one of the GDPR components should be used by apply it on a common place in your App.) + <strong>GDPR Component type:</strong> (only <strong>one of the GDPR components should be used once</strong> by apply it on a + <strong>common place in your App e.g.: MainLayout.razor</strong>!) <select class="form-control selectpicker w-25" @bind="_gdprControlType"> <option value="Banner">Banner</option> <option value="Modal">Modal</option> @@ -43,7 +44,7 @@ </div> </div> - @*THIS should be in a common part of your App e.g.: MainLayout.razor !!!*@ + @*THIS should be in a common part of your App e.g.: MainLayout.razor!!!*@ <div> <style> .gdpr-banner { @@ -106,7 +107,7 @@ else if (_gdprControlType == "Modal") </div> </div> - @*THIS should be in a common part of your App e.g.: MainLayout.razor !!!*@ + @*THIS should be in a common part of your App e.g.: MainLayout.razor!!!*@ <GdprModal @ref="_gdprModal" OverlayBackgroundColor="@_overlayColor" OverlayOpacity="@(_overlayOpacity /100)" From feb68b492286412a5c100ae6957878c2cf045e07 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Wed, 14 Jul 2021 18:32:48 +0200 Subject: [PATCH 65/69] More permalink tests. --- .../PermaLinkBlazorServerInitializerTest.cs | 58 +++++++++++++++++++ .../PermalinkBlazorWasmInitializerTest.cs | 57 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkBlazorServerInitializerTest.cs create mode 100644 src/Majorsoft.Blazor.Components.PermaLink.Tests/PermalinkBlazorWasmInitializerTest.cs diff --git a/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkBlazorServerInitializerTest.cs b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkBlazorServerInitializerTest.cs new file mode 100644 index 00000000..9f2ee5f0 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermaLinkBlazorServerInitializerTest.cs @@ -0,0 +1,58 @@ +using System; + +using Bunit; + +using Majorsoft.Blazor.Components.Common.JsInterop.Scroll; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +namespace Majorsoft.Blazor.Components.PermaLink.Tests +{ + [TestClass] + public class PermaLinkBlazorServerInitializerTest + { + private Bunit.TestContext _testContext; + private Mock<IPermaLinkWatcherService> _permaLinkWatcherServiceMock; + private Mock<IScrollHandler> _scrollHandlerMock; + + [TestInitialize] + public void Init() + { + _testContext = new Bunit.TestContext(); + + var mock = new Mock<ILogger<IPermaLinkWatcherService>>(); + _permaLinkWatcherServiceMock = new Mock<IPermaLinkWatcherService>(); + _scrollHandlerMock = new Mock<IScrollHandler>(); + + _testContext.Services.Add(new ServiceDescriptor(typeof(ILogger<IPermaLinkWatcherService>), mock.Object)); + _testContext.Services.Add(new ServiceDescriptor(typeof(IPermaLinkWatcherService), _permaLinkWatcherServiceMock.Object)); + _testContext.Services.Add(new ServiceDescriptor(typeof(IScrollHandler), _scrollHandlerMock.Object)); + } + + [TestCleanup] + public void Cleanup() + { + _testContext?.Dispose(); + } + + [TestMethod] + public void PermaLinkBlazorServerInitializer_should_not_rendered_Content() + { + var rendered = _testContext.RenderComponent<PermaLinkBlazorServerInitializer>(); + rendered.MarkupMatches(""); + } + + [ExpectedException(typeof(ApplicationException))] + [TestMethod] + public void PermaLinkBlazorServerInitializer_should_not_rendered_mulitple_instances() + { + var rendered = _testContext.RenderComponent<PermaLinkBlazorServerInitializer>(); + + var rendered2 = _testContext.RenderComponent<PermaLinkBlazorServerInitializer>(); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermalinkBlazorWasmInitializerTest.cs b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermalinkBlazorWasmInitializerTest.cs new file mode 100644 index 00000000..ed4f4484 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.PermaLink.Tests/PermalinkBlazorWasmInitializerTest.cs @@ -0,0 +1,57 @@ +using System; + +using Bunit; + +using Majorsoft.Blazor.Components.Common.JsInterop.Scroll; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +namespace Majorsoft.Blazor.Components.PermaLink.Tests +{ + [TestClass] + public class PermalinkBlazorWasmInitializerTest + { + private Bunit.TestContext _testContext; + private Mock<IPermaLinkWatcherService> _permaLinkWatcherServiceMock; + + [TestInitialize] + public void Init() + { + _testContext = new Bunit.TestContext(); + + _permaLinkWatcherServiceMock = new Mock<IPermaLinkWatcherService>(); + _permaLinkWatcherServiceMock.Setup(s => s.WatchPermaLinks()); + + + _testContext.Services.Add(new ServiceDescriptor(typeof(IPermaLinkWatcherService), _permaLinkWatcherServiceMock.Object)); + } + + [TestCleanup] + public void Cleanup() + { + _testContext?.Dispose(); + } + + [TestMethod] + public void PermalinkBlazorWasmInitializer_should_not_rendered_Content() + { + var rendered = _testContext.RenderComponent<PermalinkBlazorWasmInitializer>(); + rendered.MarkupMatches(""); + + _permaLinkWatcherServiceMock.Verify(v => v.WatchPermaLinks(), Times.Once); + } + + [ExpectedException(typeof(ApplicationException))] + [TestMethod] + public void PermalinkBlazorWasmInitializer_should_not_rendered_mulitple_instances() + { + var rendered = _testContext.RenderComponent<PermalinkBlazorWasmInitializer>(); + + var rendered2 = _testContext.RenderComponent<PermalinkBlazorWasmInitializer>(); + } + } +} \ No newline at end of file From 5e747d1c0fa48f18accea7c65c499d5e0f3343a2 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Wed, 14 Jul 2021 20:49:08 +0200 Subject: [PATCH 66/69] Unit tested GDPR consent components. --- .../GdprBannerTest.cs | 142 ++++++++++++++++++ .../GdprConsentDataTest.cs | 75 +++++++++ .../GdprModalTest.cs | 139 +++++++++++++++++ ...Blazor.Components.GdprConsent.Tests.csproj | 26 ++++ .../GdprBanner.razor | 6 +- .../GdprConsentData.cs | 6 + .../IGdprConsentService.cs | 2 +- ...ajorsoft.Blazor.Components.GdprConsent.xml | 11 +- .../Components/GdprConsents.razor | 6 +- src/Majorsoft.Blazor.Components.sln | 7 + 10 files changed, 410 insertions(+), 10 deletions(-) create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprBannerTest.cs create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprConsentDataTest.cs create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprModalTest.cs create mode 100644 src/Majorsoft.Blazor.Components.GdprConsent.Tests/Majorsoft.Blazor.Components.GdprConsent.Tests.csproj diff --git a/src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprBannerTest.cs b/src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprBannerTest.cs new file mode 100644 index 00000000..93a3b754 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprBannerTest.cs @@ -0,0 +1,142 @@ +using System; +using System.Linq; +using System.Threading.Tasks; + +using Bunit; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +namespace Majorsoft.Blazor.Components.GdprConsent.Tests +{ + [TestClass] + public class GdprBannerTest + { + private Bunit.TestContext _testContext; + private Mock<IGdprConsentService> _dprConsentServiceMock; + private Mock<IGdprConsentNotificationService> _gdprConsentNotificationServiceMock; + + [TestInitialize] + public void Init() + { + _testContext = new Bunit.TestContext(); + + _gdprConsentNotificationServiceMock = new Mock<IGdprConsentNotificationService>(); + + _dprConsentServiceMock = new Mock<IGdprConsentService>(); + _dprConsentServiceMock.SetupGet(g => g.ConsentNotificationService).Returns(_gdprConsentNotificationServiceMock.Object); + + var mock = new Mock<ILogger<GdprBanner>>(); + _testContext.Services.Add(new ServiceDescriptor(typeof(ILogger<GdprBanner>), mock.Object)); + _testContext.Services.Add(new ServiceDescriptor(typeof(IGdprConsentService), _dprConsentServiceMock.Object)); + } + + [TestCleanup] + public void Cleanup() + { + _testContext?.Dispose(); + } + + [TestMethod] + public void GdprBanner_should_not_render_anything_if_consent_valid() + { + _dprConsentServiceMock.Setup(s => s.GetGdprConsentDataAsync()) + .ReturnsAsync(new GdprConsentData() + { + AnsweredAt = DateTime.Now, + AnswerValidUntil = DateTime.Now.AddDays(1), + }); + + var rendered = _testContext.RenderComponent<GdprBanner>(); + rendered.MarkupMatches(""); + + _dprConsentServiceMock.Verify(v => v.GetGdprConsentDataAsync(), Times.Once); + } + + [TestMethod] + public void GdprBanner_should_rendered_correctly_html_attributes() + { + var rendered = _testContext.RenderComponent<GdprBanner>( + ("id", "id1"), //HTML attributes + ("title", "text"), //HTML attributes + (nameof(GdprBanner.BannerOpacity), 0.5) + ); + + var div = rendered.Find("div"); + Assert.IsNotNull(div); + + rendered.MarkupMatches("<div class=\"banner\" style=\"background-color: rgba(128,128,128, 0.50)\" id=\"id1\" title=\"text\" ></div>"); + } + + [TestMethod] + public void GdprBanner_should_rendered_correctly_BannerOpacity() + { + var rendered = _testContext.RenderComponent<GdprBanner>(parameters => parameters + .Add(p => p.BannerOpacity, 0.99)); + + var div = rendered.Find("div"); + Assert.IsNotNull(div); + + rendered.MarkupMatches("<div class=\"banner\" style=\"background-color: rgba(128,128,128, 0.99)\"></div>"); + } + + [TestMethod] + public void GdprBanner_should_rendered_correctly_BannerBackgroundColor() + { + var rendered = _testContext.RenderComponent<GdprBanner>(parameters => parameters + .Add(p => p.BannerBackgroundColor, "red")); + + var div = rendered.Find("div"); + Assert.IsNotNull(div); + + rendered.MarkupMatches("<div class=\"banner\" style=\"background-color: rgba(255,0,0, 0.90)\"></div>"); + } + + [TestMethod] + public void GdprBanner_should_rendered_correctly_Content() + { + var rendered = _testContext.RenderComponent<GdprBanner>(parameters => parameters + .Add(p => p.Content, "<div><p>Banner text</p></div>")); + + var div = rendered.Find("div"); + Assert.IsNotNull(div); + + rendered.MarkupMatches("<div class=\"banner\" style=\"background-color: rgba(128,128,128, 0.90)\"><div><p>Banner text</p></div></div>"); + } + + [TestMethod] + public async Task GdprBanner_should_accept_all_ConsentDetails() + { + var rendered = _testContext.RenderComponent<GdprBanner>(parameters => parameters + .Add(p => p.ConsentDetails, new GdprConsentDetail[] + { + new GdprConsentDetail() { ConsentName = "All", IsAccepted = false }, + new GdprConsentDetail() { ConsentName = "Tracking" }, + new GdprConsentDetail() { ConsentName = "Session" }, + })); + + await rendered.Instance.AcceptAll(); + + _dprConsentServiceMock.Verify(v => v.SetGdprConsentDataAsync(It.Is<GdprConsentData>(v => v.AllAccepted)), Times.Once); + } + + [TestMethod] + public async Task GdprBanner_should_reject_all_ConsentDetails() + { + var rendered = _testContext.RenderComponent<GdprBanner>(parameters => parameters + .Add(p => p.ConsentDetails, new GdprConsentDetail[] + { + new GdprConsentDetail() { ConsentName = "All", IsAccepted = true }, + new GdprConsentDetail() { ConsentName = "Tracking" }, + new GdprConsentDetail() { ConsentName = "Session" }, + })); + + await rendered.Instance.RejectAll(); + + _dprConsentServiceMock.Verify(v => v.SetGdprConsentDataAsync(It.Is<GdprConsentData>(v => v.GdprConsentDetails.All(x => !x.IsAccepted))), Times.Once); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprConsentDataTest.cs b/src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprConsentDataTest.cs new file mode 100644 index 00000000..aea51cd6 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprConsentDataTest.cs @@ -0,0 +1,75 @@ +using System; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Majorsoft.Blazor.Components.GdprConsent.Tests +{ + [TestClass] + public class GdprConsentDataTest + { + [TestMethod] + public void GdprConsentData_GdprConsentDetails_initialized() + { + var data = new GdprConsentData(); + + Assert.IsNotNull(data.GdprConsentDetails); + } + + [TestMethod] + public void GdprConsentData_IsValid_true() + { + var data = new GdprConsentData() + { + AnsweredAt = DateTime.Now, + AnswerValidUntil = DateTime.Now.AddHours(1) + }; + + Assert.AreEqual(true, data.IsValid); + } + + [TestMethod] + public void GdprConsentData_IsValid_false() + { + var data = new GdprConsentData() + { + AnsweredAt = DateTime.Now, + AnswerValidUntil = DateTime.Now.AddHours(-1) + }; + + Assert.AreEqual(false, data.IsValid); + } + + [TestMethod] + public void GdprConsentData_AllAccepted_true() + { + var data = new GdprConsentData() + { + GdprConsentDetails = new GdprConsentDetail[] + { + new GdprConsentDetail() { IsAccepted = true }, + new GdprConsentDetail() { IsAccepted = true }, + new GdprConsentDetail() { IsAccepted = true }, + } + }; + + Assert.AreEqual(true, data.AllAccepted); + } + + [TestMethod] + public void GdprConsentData_AllAccepted_false() + { + var data = new GdprConsentData() + { + GdprConsentDetails = new GdprConsentDetail[] + { + new GdprConsentDetail() { IsAccepted = true }, + new GdprConsentDetail() { IsAccepted = true }, + new GdprConsentDetail(), + } + }; + + Assert.AreEqual(false, new GdprConsentData().AllAccepted); + Assert.AreEqual(false, data.AllAccepted); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprModalTest.cs b/src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprModalTest.cs new file mode 100644 index 00000000..8d9f40ee --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent.Tests/GdprModalTest.cs @@ -0,0 +1,139 @@ +using System; +using System.Threading.Tasks; + +using Bunit; + +using Majorsoft.Blazor.Components.Common.JsInterop.Focus; +using Majorsoft.Blazor.Components.CssEvents.Transition; +using Majorsoft.Blazor.Components.Modal; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Moq; + +namespace Majorsoft.Blazor.Components.GdprConsent.Tests +{ + [TestClass] + public class GdprModalTest + { + private Bunit.TestContext _testContext; + private Mock<IGdprConsentService> _dprConsentServiceMock; + private Mock<IGdprConsentNotificationService> _gdprConsentNotificationServiceMock; + private Mock<ITransitionEventsService> _transitionMock; + private Mock<IFocusHandler> _focusHandlerMock; + + [TestInitialize] + public void Init() + { + _testContext = new Bunit.TestContext(); + + _gdprConsentNotificationServiceMock = new Mock<IGdprConsentNotificationService>(); + + _dprConsentServiceMock = new Mock<IGdprConsentService>(); + _dprConsentServiceMock.SetupGet(g => g.ConsentNotificationService).Returns(_gdprConsentNotificationServiceMock.Object); + + var mock = new Mock<ILogger<GdprModal>>(); + var logger = new Mock<ILogger<ModalDialog>>(); + _transitionMock = new Mock<ITransitionEventsService>(); + _focusHandlerMock = new Mock<IFocusHandler>(); + + _testContext.Services.Add(new ServiceDescriptor(typeof(ILogger<GdprModal>), mock.Object)); + _testContext.Services.Add(new ServiceDescriptor(typeof(ILogger<ModalDialog>), logger.Object)); + _testContext.Services.Add(new ServiceDescriptor(typeof(IGdprConsentService), _dprConsentServiceMock.Object)); + _testContext.Services.Add(new ServiceDescriptor(typeof(ITransitionEventsService), _transitionMock.Object)); + _testContext.Services.Add(new ServiceDescriptor(typeof(IFocusHandler), _focusHandlerMock.Object)); + } + + [TestCleanup] + public void Cleanup() + { + _testContext?.Dispose(); + } + + [TestMethod] + public void GdprBanner_should_not_render_anything_if_consent_valid() + { + _dprConsentServiceMock.Setup(s => s.GetGdprConsentDataAsync()) + .ReturnsAsync(new GdprConsentData() + { + AnsweredAt = DateTime.Now, + AnswerValidUntil = DateTime.Now.AddDays(1), + }); + + var rendered = _testContext.RenderComponent<GdprModal>(); + rendered.MarkupMatches(""); + + _dprConsentServiceMock.Verify(v => v.GetGdprConsentDataAsync(), Times.Once); + } + + [Ignore] //TODO: does not work because of StateHasChanged() and dialog opens up in Render event. + [TestMethod] + public void ModalDialog_should_rendered_correctly_html_attributes() + { + var rendered = _testContext.RenderComponent<GdprModal>( + ("id", "id1"), //HTML attributes + ("title", "text"), //HTML attributes + (nameof(ModalDialog.OverlayOpacity), 0.5) + ); + + + var div = rendered.Find("div"); + rendered.Render(); + + Assert.IsNotNull(div); + + rendered.WaitForAssertion(() => rendered.MarkupMatches(@"<div class=""bmodal fade"" style=""opacity: 1; background-color: rgba(128, 128, 128, 0.90)"" id=""id1"" title=""text""> + <div class=""bmodal-content dynamicStyle"" tabindex=""100""> + <div class=""bmodal-header""> + <button type = ""button"" class=""close""> + <span aria-hidden=""true"">x</span> + <span class=""sr-only"">Close</span> + </button> + </div> + <div class=""bmodal-body""></div> + </div> + </div> + <style> + .fade { + transition: opacity 0.25s linear; + } + .dynamicStyle { + top: calc(15% + 0px); + left: 50%; + min-width:200px; + min-height:200px; + width:auto; + height:auto; + transition: top 0.25s ease-in-out; + } + </style>")); + } + + [TestMethod] + public async Task GdprModal_should_SaveChoice_as_user_choosen_ConsentDetails() + { + _dprConsentServiceMock.Setup(s => s.GetGdprConsentDataAsync()) + .ReturnsAsync(new GdprConsentData() + { + AnsweredAt = DateTime.Now, + AnswerValidUntil = DateTime.Now.AddDays(1), + }); + var details = new GdprConsentDetail[] + { + new GdprConsentDetail() { ConsentName = "All", IsAccepted = false }, + new GdprConsentDetail() { ConsentName = "Tracking", IsAccepted = true }, + new GdprConsentDetail() { ConsentName = "Session", IsAccepted = true }, + }; + + var rendered = _testContext.RenderComponent<GdprModal>(parameters => parameters + .Add(p => p.ConsentDetails, details)); + + await rendered.Instance.SaveChoice(); + + _dprConsentServiceMock.Verify(v => v.SetGdprConsentDataAsync(It.Is<GdprConsentData>(v => !details[0].IsAccepted && details[1].IsAccepted && details[2].IsAccepted)), + Times.Once); + } + } +} \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent.Tests/Majorsoft.Blazor.Components.GdprConsent.Tests.csproj b/src/Majorsoft.Blazor.Components.GdprConsent.Tests/Majorsoft.Blazor.Components.GdprConsent.Tests.csproj new file mode 100644 index 00000000..f63344e3 --- /dev/null +++ b/src/Majorsoft.Blazor.Components.GdprConsent.Tests/Majorsoft.Blazor.Components.GdprConsent.Tests.csproj @@ -0,0 +1,26 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net5.0</TargetFramework> + + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="bunit" Version="1.1.5" /> + <PackageReference Include="bunit.web" Version="1.1.5" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" /> + <PackageReference Include="Moq" Version="4.16.1" /> + <PackageReference Include="MSTest.TestAdapter" Version="2.2.4" /> + <PackageReference Include="MSTest.TestFramework" Version="2.2.4" /> + <PackageReference Include="coverlet.collector" Version="3.0.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Majorsoft.Blazor.Components.GdprConsent\Majorsoft.Blazor.Components.GdprConsent.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor index 1455dea9..f09a7901 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor @@ -83,7 +83,7 @@ /// Accepting all GDPR Consents. /// </summary> /// <returns>ValueTask</returns> - public async ValueTask Accepted() + public async ValueTask AcceptAll() { await _gdprConsentService.SetGdprConsentDataAsync(CreateGdprConsentData(true)); WriteDiag("GDPR Consent was accepted."); @@ -93,7 +93,7 @@ /// Rejecting all GDPR Consents. /// </summary> /// <returns>ValueTask</returns> - public async ValueTask Rejected() + public async ValueTask RejectAll() { await _gdprConsentService.SetGdprConsentDataAsync(CreateGdprConsentData(false)); WriteDiag("GDPR Consent was rejected."); @@ -129,7 +129,7 @@ /// <summary> /// Component dispose /// </summary> - public async void Dispose() + public void Dispose() { Initialized = false; _gdprConsentService.ConsentNotificationService.GdprConsentStateChanged -= CheckConsent; diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentData.cs b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentData.cs index 49a6ab3d..94a1ee1d 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentData.cs +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprConsentData.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Majorsoft.Blazor.Components.GdprConsent { @@ -28,6 +29,11 @@ public class GdprConsentData /// </summary> public bool IsValid => AnswerValidUntil >= DateTime.Now; + /// <summary> + /// Gets weather all Consent were accepted. + /// </summary> + public bool AllAccepted => (GdprConsentDetails?.Any() ?? false) && (GdprConsentDetails?.All(x => x.IsAccepted) ?? false); + /// <summary> /// Default constructor /// </summary> diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs index 70cc68ad..2d17583b 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs +++ b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs @@ -8,7 +8,7 @@ namespace Majorsoft.Blazor.Components.GdprConsent public interface IGdprConsentService { /// <summary> - /// Gets GDPR Consent Browser storage key name + /// Gets GDPR Consent Browser local storage key name /// </summary> string ConsentStoreKeyName { get; } diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml index e54aa8cf..ef132897 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml @@ -35,6 +35,11 @@ Gets weather the Consent answer is valid or not. </summary> </member> + <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprConsentData.AllAccepted"> + <summary> + Gets weather all Consent were accepted. + </summary> + </member> <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprConsentData.#ctor"> <summary> Default constructor @@ -98,7 +103,7 @@ </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.ConsentStoreKeyName"> <summary> - Gets GDPR Consent Browser storage key name + Gets GDPR Consent Browser local storage key name </summary> </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.ConsentNotificationService"> @@ -232,13 +237,13 @@ Arbitrary HTML attributes e.g.: tabindex="1" will be passed to the corresponding rendered HTML element. </summary> </member> - <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.Accepted"> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.AcceptAll"> <summary> Accepting all GDPR Consents. </summary> <returns>ValueTask</returns> </member> - <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.Rejected"> + <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.RejectAll"> <summary> Rejecting all GDPR Consents. </summary> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor index 040e6714..7b169336 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor @@ -71,10 +71,10 @@ <span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent Banner usage in your Blazor Application.</strong> - <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprBanner.Accepted()">I agree</button> - <button type="button" class="btn btn-secondary m-1" @onclick="async () => await _gdprBanner.Rejected()">Disagree</button> + <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprBanner.AcceptAll()">I agree</button> + <button type="button" class="btn btn-secondary m-1" @onclick="async () => await _gdprBanner.RejectAll()">Disagree</button> - <button type="button" class="btn btn-warning m-1" @onclick='() => { _gdprControlType = "Modal"; }'>Customize</button> + <button type="button" class="btn btn-warning m-1 ml-4" @onclick='() => { _gdprControlType = "Modal"; }'>Customize</button> </div> </Content> </GdprBanner> diff --git a/src/Majorsoft.Blazor.Components.sln b/src/Majorsoft.Blazor.Components.sln index 1a4f5bb9..3cc4c8c6 100644 --- a/src/Majorsoft.Blazor.Components.sln +++ b/src/Majorsoft.Blazor.Components.sln @@ -93,6 +93,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Components EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Majorsoft.Blazor.Components.GdprConsent", "Majorsoft.Blazor.Components.GdprConsent\Majorsoft.Blazor.Components.GdprConsent.csproj", "{E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Majorsoft.Blazor.Components.GdprConsent.Tests", "Majorsoft.Blazor.Components.GdprConsent.Tests\Majorsoft.Blazor.Components.GdprConsent.Tests.csproj", "{D7481B09-8B56-4FC7-BDE3-EEE54AB841AC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -239,6 +241,10 @@ Global {E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC}.Release|Any CPU.Build.0 = Release|Any CPU + {D7481B09-8B56-4FC7-BDE3-EEE54AB841AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7481B09-8B56-4FC7-BDE3-EEE54AB841AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7481B09-8B56-4FC7-BDE3-EEE54AB841AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7481B09-8B56-4FC7-BDE3-EEE54AB841AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -278,6 +284,7 @@ Global {A1FF7E56-1ADE-4A53-A517-A9A0F3A70ECC} = {B6905E0B-759A-4928-B38A-9210B5CC8988} {5A9DBE07-4666-4DEF-841A-477F2A1A28B9} = {020CAF9C-D289-470A-BB97-E58D73DDCE70} {E3B6CBB8-619B-4F09-93F2-BA97BFD3C0EC} = {B6905E0B-759A-4928-B38A-9210B5CC8988} + {D7481B09-8B56-4FC7-BDE3-EEE54AB841AC} = {020CAF9C-D289-470A-BB97-E58D73DDCE70} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F3E8B7F7-3F7C-4D6C-8B7B-48F1AF4694B9} From 58be526d31472b4ea69f0bd93ee2b8d571b09fe5 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Thu, 15 Jul 2021 19:33:16 +0200 Subject: [PATCH 67/69] GDPR consents docs. --- .github/docs/GdprConsent.md | 206 ++++++++++++++++++ .github/docs/Inputs.md | 2 +- README.md | 1 + ...rsoft.Blazor.Components.GdprConsent.csproj | 2 +- .../Components/GdprConsents.razor | 2 +- 5 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 .github/docs/GdprConsent.md diff --git a/.github/docs/GdprConsent.md b/.github/docs/GdprConsent.md new file mode 100644 index 00000000..a080ffa9 --- /dev/null +++ b/.github/docs/GdprConsent.md @@ -0,0 +1,206 @@ +Blazor GDPR Consent controls +============ +[![Build Status](https://dev.azure.com/major-soft/GitHub/_apis/build/status/blazor-components/blazor-components-build-check)](https://dev.azure.com/major-soft/GitHub/_build/latest?definitionId=6) +[![Package Version](https://img.shields.io/nuget/v/Majorsoft.Blazor.Components.GdprConsent?label=Latest%20Version)](https://www.nuget.org/packages/Majorsoft.Blazor.Components.GdprConsent/) +[![NuGet Downloads](https://img.shields.io/nuget/dt/Majorsoft.Blazor.Components.GdprConsent?label=Downloads)](https://www.nuget.org/packages/Majorsoft.Blazor.Components.GdprConsent/) +[![License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/majorimi/blazor-components/blob/master/LICENSE) + +# About + +Blazor injectable `IGdprConsentService` service and components that renders a customizable GDPR consent Banner or Popup witch Accept/Reject for cookie settings chosen value is persisted to Browser storage. +To initialize GDPR Consents use `GdprBanner` or `GdprModal` only once in your Blazor App MainLayout.razor page or any common place. + +**All components work with WebAssembly and Server hosted models**. +For code examples [see usage](https://github.com/majorimi/blazor-components/blob/master/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor). + +You can try it out by using the [demo app](https://blazorextensions.z6.web.core.windows.net/gdpr). + +![GDPR Consent demo](https://github.com/majorimi/blazor-components-docs/raw/main/github/docs/gifs/gdprConsents.gif) + +# Components and Services + +- **`GdprBanner`**: renders a small Overlay layer at the bottom of the page with customizable content for showing the given GDPR message. +- **`GdprModal`**: renders a Modal dialog with Overlay layer for the whole page with customizable content for showing the given GDPR message. +- **`IGdprConsentService`**: injectable service to handle GDPR Consent actions. +- **`IGdprConsentNotificationService`**: injectable singleton service to handle GDPR Consent changes. + +## `GdprBanner` component + +Modal dialogs are positioned over everything else in the document with optional Overlay (customizable opacity and color). +Modal dialogs are removed from DOM when closed. **Only one modal window can be shown at a time.** +Dialog automatically (can be disabled) focuses itself to capture keyboard events when closed will refocus the last element. + +### Properties +- **`Header`: `RenderFragment` HTML content** <br /> +HTML content to show on the Modal header (top), right to the close button (if visible). Can be any valid HTML but should be only Title text. +**Must not be defined if you want to leave it out. Also `ShowCloseButton` must be set to `false`** +- **`Content`: `RenderFragment` HTML content - Required** <br /> +Required HTML content to show on the Modal dialog. Can be any valid HTML. +- **`Footer`: `RenderFragment` HTML content** <br /> +HTML content to show on the Modal footer (bottom). Can be any valid HTML but should be only custom action buttons. +**Must not be defined if you want to leave it out.** +- **`OverlayBackgroundColor`: `string { get; set; }` (default: "gray")** <br /> + Sets the `style` of the HTML `<div>` `background-color`. Use HTML specified: **Color Names**, **RGB** or with **HEX** values. +- **`OverlayOpacity`: `double { get; set; }` (default: 0.9)** <br /> +Opacity of the overlay `<div>`. Value should be **between 0..1**. Where 0 means the overlay layer is not visible. +- **`CloseOnOverlayClick`: `bool { get; set; }` (default: true)** <br /> +When `true` Modal dialog will be closed when Overlay (background) clicked. It works even if Overlay not visible (Opacity is set to 0) +- **`CloseOnEscapeKey`: `bool { get; set; }` (default: true)** <br /> +When `true` Modal dialog will be closed when **Esc** (Escape) key pressed. +- **`Height`: `double { get; set; }` (default: 0)** <br /> +Modal dialog window Height in **px** if set to **0** Height is set **auto**. +- **`Width`: `double { get; set; }` (default: 0)** <br /> +Modal dialog window Width in **px** if set to **0** Width is set **auto**. +- **`MinHeight`: `double { get; set; }` (default: 200)** <br /> +Modal dialog window minimum Height in **px**. +- **`MinWidth`: `double { get; set; }` (default: 200)** <br /> +Modal dialog window minimum Width in **px**. +- **`Focus`: `bool { get; set; }` (default: true)** <br /> +When `true` Modal dialog will automatically set focus to itself when it opens, and set it bact to the last focused element when it closes. +In general this should never be set to false as it makes the Modal less accessible to screen-readers, etc. +- **`Animate`: `bool { get; set; }` (default: true)** <br /> +When `true` Modal dialog will appear and disappear by using smooth CSS slide and fade transitions. +- **`Centered`: `bool { get; set; }` (default: false)** <br /> +When `true` Modal dialog will be vertically centered, otherwise shown near to the top. Modal dialog horizontally always centered. +- **`ShowCloseButton`: `bool { get; set; }` (default: true)** <br /> + When `true` Modal dialog will show Header (even if Header is not defined) with closed **x** button. +- **`IsOpen`: `bool { get; }`** <br /> + Returns `true` if the Modal dialog is opened, otherwise `false`. + +**Arbitrary HTML attributes e.g.: `id="diag1"` will be passed to the corresponding rendered root HTML element Overlay `<div>`**. + +### Functions +- **`Open()`: `Task Open()`** <br /> +When method called Modal dialog will be opened. It should be `await`-ed. +- **`Close()`: `Task Close()`** <br /> +When method called Modal dialog will be closed. It should be `await`-ed. +- **`DisposeAsync()`: `Task DisposeAsync()`** <br /> +Component implements `IAsyncDisposable` interface Blazor framework will call it when parent removed from render tree. + +## `GdprModal` component + +Modal dialogs are positioned over everything else in the document with optional Overlay (customizable opacity and color). +Modal dialogs are removed from DOM when closed. **Only one modal window can be shown at a time.** +Dialog automatically (can be disabled) focuses itself to capture keyboard events when closed will refocus the last element. + +### Properties +- **`Header`: `RenderFragment` HTML content** <br /> +HTML content to show on the Modal header (top), right to the close button (if visible). Can be any valid HTML but should be only Title text. +**Must not be defined if you want to leave it out. Also `ShowCloseButton` must be set to `false`** +- **`Content`: `RenderFragment` HTML content - Required** <br /> +Required HTML content to show on the Modal dialog. Can be any valid HTML. +- **`Footer`: `RenderFragment` HTML content** <br /> +HTML content to show on the Modal footer (bottom). Can be any valid HTML but should be only custom action buttons. +**Must not be defined if you want to leave it out.** +- **`OverlayBackgroundColor`: `string { get; set; }` (default: "gray")** <br /> + Sets the `style` of the HTML `<div>` `background-color`. Use HTML specified: **Color Names**, **RGB** or with **HEX** values. +- **`OverlayOpacity`: `double { get; set; }` (default: 0.9)** <br /> +Opacity of the overlay `<div>`. Value should be **between 0..1**. Where 0 means the overlay layer is not visible. +- **`CloseOnOverlayClick`: `bool { get; set; }` (default: true)** <br /> +When `true` Modal dialog will be closed when Overlay (background) clicked. It works even if Overlay not visible (Opacity is set to 0) +- **`CloseOnEscapeKey`: `bool { get; set; }` (default: true)** <br /> +When `true` Modal dialog will be closed when **Esc** (Escape) key pressed. +- **`Height`: `double { get; set; }` (default: 0)** <br /> +Modal dialog window Height in **px** if set to **0** Height is set **auto**. +- **`Width`: `double { get; set; }` (default: 0)** <br /> +Modal dialog window Width in **px** if set to **0** Width is set **auto**. +- **`MinHeight`: `double { get; set; }` (default: 200)** <br /> +Modal dialog window minimum Height in **px**. +- **`MinWidth`: `double { get; set; }` (default: 200)** <br /> +Modal dialog window minimum Width in **px**. +- **`Focus`: `bool { get; set; }` (default: true)** <br /> +When `true` Modal dialog will automatically set focus to itself when it opens, and set it bact to the last focused element when it closes. +In general this should never be set to false as it makes the Modal less accessible to screen-readers, etc. +- **`Animate`: `bool { get; set; }` (default: true)** <br /> +When `true` Modal dialog will appear and disappear by using smooth CSS slide and fade transitions. +- **`Centered`: `bool { get; set; }` (default: false)** <br /> +When `true` Modal dialog will be vertically centered, otherwise shown near to the top. Modal dialog horizontally always centered. +- **`ShowCloseButton`: `bool { get; set; }` (default: true)** <br /> + When `true` Modal dialog will show Header (even if Header is not defined) with closed **x** button. +- **`IsOpen`: `bool { get; }`** <br /> + Returns `true` if the Modal dialog is opened, otherwise `false`. + +**Arbitrary HTML attributes e.g.: `id="diag1"` will be passed to the corresponding rendered root HTML element Overlay `<div>`**. + +### Functions +- **`Open()`: `Task Open()`** <br /> +When method called Modal dialog will be opened. It should be `await`-ed. +- **`Close()`: `Task Close()`** <br /> +When method called Modal dialog will be closed. It should be `await`-ed. +- **`DisposeAsync()`: `Task DisposeAsync()`** <br /> +Component implements `IAsyncDisposable` interface Blazor framework will call it when parent removed from render tree. + + +# Configuration + +## Installation + +**Majorsoft.Blazor.Components.GdprConsent** is available on [NuGet](https://www.nuget.org/packages/Majorsoft.Blazor.Components.GdprConsent). + +```sh +dotnet add package Majorsoft.Blazor.Components.GdprConsent +``` +Use the `--version` option to specify a [preview version](https://www.nuget.org/packages/Majorsoft.Blazor.Components.GdprConsent/absoluteLatest) to install. + +## Usage + +Add using statement to your Blazor `<component/page>.razor` file. Or globally reference it into `_Imports.razor` file. + +``` +@using Majorsoft.Blazor.Components.Toggle +@using Majorsoft.Blazor.Extensions.BrowserStorage +@using Majorsoft.Blazor.Components.Modal +@using Majorsoft.Blazor.Components.Common.JsInterop.Focus +@using Majorsoft.Blazor.Components.CssEvents +@using Majorsoft.Blazor.Components.GdprConsent +``` + +### Dependences +**Majorsoft.Blazor.Components.GdprConsent** package depends on other Majorsoft Nuget packages: +- [Majorsoft.Blazor.Components.CssEvents](https://www.nuget.org/packages/Majorsoft.Blazor.Components.CssEvents) +which handles CSS Transition and Animation events for the dialog animation. +- [Majorsoft.Blazor.Components.Common.JsInterop](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Common.JsInterop) +which handles JS Interop for focusing previous elements. +- [Majorsoft.Blazor.Components.CssEvents](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Modal) +which renders Model dialog window for `GdprModal`. +- [Majorsoft.Blazor.Components.CssEvents](https://www.nuget.org/packages/Majorsoft.Blazor.Components.Toggle) +which can be used to customize `` with ON/OFF switches to allow Cookie or not. +- [Majorsoft.Blazor.Components.CssEvents](https://www.nuget.org/packages/) +which stores user provided Consents in the Browser's Local storage. + +**In case of WebAssembly project register services in your `Program.cs` file:** +``` +using Majorsoft.Blazor.Components.GdprConsent; +... +public static async Task Main(string[] args) +{ + var builder = WebAssemblyHostBuilder.CreateDefault(args); + + //Register dependencies + builder.Services.AddGdprConsent(); +} +``` + +**In case of Server hosted project register services in your `Startup.cs` file:** +``` +using Majorsoft.Blazor.Components.GdprConsent; +... + +public void ConfigureServices(IServiceCollection services) +{ + //Register dependencies + services.AddGdprConsent(); +} +``` + +### `GdprBanner` usage + +``` + +``` + +### `GdprModal` usage + +``` + +``` \ No newline at end of file diff --git a/.github/docs/Inputs.md b/.github/docs/Inputs.md index dcc4b2e0..cf1c2931 100644 --- a/.github/docs/Inputs.md +++ b/.github/docs/Inputs.md @@ -13,7 +13,7 @@ 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/maxLengthInput). -![Debounce demo](https://github.com/majorimi/blazor-components-docs/raw/main/github/docs/gifs/maxLengthInput.gif) +![Inputs demo](https://github.com/majorimi/blazor-components-docs/raw/main/github/docs/gifs/maxLengthInput.gif) # Components diff --git a/README.md b/README.md index bc251d0e..dd1b897e 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ Check out our planned components and extensions on the project [Wiki page](https - **Majorsoft.Blazor.Components.Tabs**: [Tabs components](https://github.com/majorimi/blazor-components/blob/master/.github/docs/Tabs.md) that renders customizable Tabs panel with many tabs and custom content. - **Majorsoft.Blazor.Components.Collapse**: [Collapse components](https://github.com/majorimi/blazor-components/blob/master/.github/docs/Collapse.md) that renders customizable Collapsable/Expandable panel and Accordion with many but only one active panel also custom content and header. - **Majorsoft.Blazor.Components.Maps**: [Google/Bing Maps components](https://github.com/majorimi/blazor-components/blob/master/.github/docs/Maps.md) that renders **Google/Bing maps** wrapped into Blazor components allowing to control and mange maps with .Net code. +- **Majorsoft.Blazor.Components.GdprConsent**: [GDPR Consent components](https://github.com/majorimi/blazor-components/blob/master/.github/docs/GdprConsent.md) injectable service and components that renders a customizable GDPR consent Banner or Popup witch Accept/Reject for cookie settings chosen value is persisted to Browser storage. ## Other info - [Contributing](CONTRIBUTING.md) diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj index dc3ce3a6..7e497217 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.csproj @@ -17,7 +17,7 @@ <RepositoryType>Git</RepositoryType> <PackageTags>.Net5 Blazor GDPR Consent Law Banner ModalWindow Modal Cookie Cookies</PackageTags> <Title>Blazor Components GDPR Consent banner and popup</Title> - <Description>Blazor component that renders a customizable GDPR consent Banner or Popup witch Accept/Reject for cookie settings chosen value is persisted to Browser storage. Part of Majorsoft Blazor library.</Description> + <Description>Blazor injectable IGdprConsentService service and components that renders a customizable GDPR consent Banner or Popup witch Accept/Reject for cookie settings chosen value is persisted to Browser storage. To initialize GDPR Consents use GdprBanner or GdprModal only once in your Blazor App MainLayout.razor page or any common place. Part of Majorsoft Blazor library.</Description> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> diff --git a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor index 7b169336..60af3f3f 100644 --- a/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor +++ b/src/Majorsoft.Blazor.Components.TestApps.Common/Components/GdprConsents.razor @@ -249,7 +249,7 @@ else if (_gdprControlType == "Modal") StateHasChanged(); } - public async void Dispose() + public void Dispose() { _gdprConsentService.ConsentNotificationService.GdprConsentStateChanged -= OnConsentChanged; //Unsubscribe from change event } From 27e63ea87dfe25346557eb3b4a4166c27ce5e0c9 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Thu, 15 Jul 2021 20:12:20 +0200 Subject: [PATCH 68/69] Updated demo app to Nuget 1.4.0 --- .../Components/BrowserStorage.razor | 9 +- .../Components/Collapse.razor | 6 +- .../Components/ColorPicker.razor | 2 +- .../Components/CssEvents.razor | 2 + .../Components/Debounce.razor | 14 +- .../Components/Dialog.razor | 40 +-- .../Components/GdprConsents.razor | 256 ++++++++++++++++++ .../Components/Index.razor | 10 +- .../Components/JSInterop.razor | 20 +- .../Components/JsDemo/BrowserDateJs.razor | 24 ++ .../Components/JsDemo/BrowserThemeJs.razor | 58 ++++ .../Components/JsDemo/LangJs.razor | 6 +- .../Components/JsDemo/MouseJs.razor | 5 +- .../Components/JsDemo/ResizeJs.razor | 5 +- .../Components/JsDemo/ScrollJs.razor | 85 +++++- .../Components/Loading.razor | 19 +- .../Components/Logger.razor | 4 +- .../Components/Maps.razor | 2 + .../Components/MapsGoogle.razor | 12 +- .../Components/MaxLengthInputs.razor | 197 ++++++++++++++ .../Components/Permalink.razor | 6 +- .../Components/SiteAnalytics.razor | 83 ++++++ .../Components/Tabs.razor | 8 +- .../Components/TimerComponent.razor | 2 +- .../Components/Toggle.razor | 6 +- .../Components/Typeahead.razor | 3 +- ...Majorsoft.Blazor.Components.DemoApp.csproj | 33 ++- .../Pages/AnalyticsPage.razor | 3 + .../Pages/GdprConsentPage.razor | 3 + .../Pages/MaxLengthInputPage.razor | 3 + .../Program.cs | 6 + .../Shared/MainLayout.razor | 20 +- .../Shared/NavMenu.razor | 13 +- .../Shared/PageScroll.razor | 16 ++ .../_Imports.razor | 11 +- .../wwwroot/blazor.components.png | Bin 0 -> 8130 bytes 36 files changed, 898 insertions(+), 94 deletions(-) create mode 100644 demo/Majorsoft.Blazor.Components.DemoApp/Components/GdprConsents.razor create mode 100644 demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/BrowserDateJs.razor create mode 100644 demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/BrowserThemeJs.razor create mode 100644 demo/Majorsoft.Blazor.Components.DemoApp/Components/MaxLengthInputs.razor create mode 100644 demo/Majorsoft.Blazor.Components.DemoApp/Components/SiteAnalytics.razor create mode 100644 demo/Majorsoft.Blazor.Components.DemoApp/Pages/AnalyticsPage.razor create mode 100644 demo/Majorsoft.Blazor.Components.DemoApp/Pages/GdprConsentPage.razor create mode 100644 demo/Majorsoft.Blazor.Components.DemoApp/Pages/MaxLengthInputPage.razor create mode 100644 demo/Majorsoft.Blazor.Components.DemoApp/Shared/PageScroll.razor create mode 100644 demo/Majorsoft.Blazor.Components.DemoApp/wwwroot/blazor.components.png diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/BrowserStorage.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/BrowserStorage.razor index 4e214371..98e6e0fa 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/BrowserStorage.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/BrowserStorage.razor @@ -1,4 +1,6 @@ -<div class="container-fluid p-3 mb-3 border rounded"> +<PageScroll /> + +<div class="container-fluid p-3 mb-3 border rounded"> <h1>Browser Storage extensions</h1> <p> Enables <a href="https://developer.mozilla.org/en-US/docs/Web/API/Storage" target="_blank">Browser Local and Session</a> storages and @@ -191,8 +193,11 @@ public int Age { get; set; } } - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + //LocalStorage await InsertLocalStorageItems(); _localStorageCount = await _localStorageService.CountAsync(); diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Collapse.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Collapse.razor index e53a281e..6a36b07e 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Collapse.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Collapse.razor @@ -1,6 +1,8 @@ -<h1>Collapse Components</h1> +<PageScroll /> + +<h1>Collapse Components</h1> <p> - Blazor component that renders customizable Collapsible/Expandable panel and Accordion with many but only one active panel also custom content and header. For usege see soruce code and docs on + Blazor component that renders customizable Collapsible/Expandable panel and Accordion with many but only one active panel also custom content and header. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Collapse.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Collapse</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Collapse" target="_blank">Nuget</a> </p> diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/ColorPicker.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/ColorPicker.razor index da7e53f3..e39b7415 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/ColorPicker.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/ColorPicker.razor @@ -6,7 +6,7 @@ textarea { <h1>Html Color Picker control</h1> <p> - Blazor components that renders a Blazor Color Picker control with color info. For usege see soruce code and docs on + Blazor components that renders a Blazor Color Picker control with color info. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/ColorPicker.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.ColorPicker</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.ColorPicker" target="_blank">Nuget</a> </p> diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/CssEvents.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/CssEvents.razor index e734e8b1..4dccb7af 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/CssEvents.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/CssEvents.razor @@ -12,6 +12,8 @@ <li><NavLink href="cssevents#animation">Animations</NavLink></li> </ul> +<PageScroll /> + <CssEventsTransition /> <CssEventsAnimation /> \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Debounce.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Debounce.razor index b78c3384..f8158b1b 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Debounce.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Debounce.razor @@ -1,6 +1,8 @@ -<h1>Deboudnce Input controls</h1> +<PageScroll /> + +<h1>Deboudnce Input controls</h1> <p> - Blazor component that renders an Input, InputText, Textarea or InputTextarea, etc. element with debounced onChange. For usege see soruce code and docs on + Blazor component that renders an Input, InputText, Textarea or InputTextarea, etc. element with debounced onChange. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/DebounceInputs.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Debounce</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Debounce" target="_blank">Nuget</a> </p> @@ -22,7 +24,7 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - <DebounceInput id="in1" class="form-control w-100" placeholder="@("Please type in at least: " + _minCharsLength + " char(s)")" autocomplete="off" + <DebounceInput id="in1" class="form-control w-100" placeholder="@($"Please type in at least: {_minCharsLength} char(s)")" autocomplete="off" @ref="input1" @bind-Value="@_debounceInputValue" @bind-Value:event="OnInput" DebounceTime="@_debounceMilisec" @@ -60,7 +62,7 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> @*<InputText @bind-Value="exampleModel.Name" />*@ - <DebounceInputText class="form-control w-100" placeholder="@("Please type in at least: " + _debounceInputTextMinCharsLength + " char(s)")" + <DebounceInputText class="form-control w-100" placeholder="@($"Please type in at least: {_debounceInputTextMinCharsLength} char(s)")" @ref="inputText1" @bind-Value="exampleModel.Name" DebounceTime="@_debounceInputTextDebounceMilisec" @@ -96,7 +98,7 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - <DebounceTextArea class="form-control w-100" placeholder="@("Please type in at least: " + _debounceTextAreaMinCharsLength + " char(s)")" + <DebounceTextArea class="form-control w-100" placeholder="@($"Please type in at least: {_debounceTextAreaMinCharsLength} char(s)")" @ref="text1" @bind-Value="@_debounceTextAreaText" @bind-Value:event="OnInput" DebounceTime="@_debounceTextAreaMilisec" @@ -133,7 +135,7 @@ <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> @*<InputTextArea @bind-Value="exampleModel2.Name" />*@ - <DebounceInputTextArea class="form-control w-100" placeholder="@("Please type in at least: " + _debounceInputTextAreaMinCharsLength + " char(s)")" + <DebounceInputTextArea class="form-control w-100" placeholder="@($"Please type in at least: {_debounceInputTextAreaMinCharsLength} char(s)")" @ref="inputTextArea1" @bind-Value="exampleModel2.Name" DebounceTime="@_debounceInputTextAreaMilisec" diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Dialog.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Dialog.razor index d19078d4..f8d5c2e6 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Dialog.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Dialog.razor @@ -1,6 +1,8 @@ -<h1>Modal Component</h1> +<PageScroll /> + +<h1>Modal Component</h1> <p> - Blazor component that can be used to render Modal dialog window with customizable content and parameterized Overlay, etc.. For usege see soruce code and docs on + Blazor component that can be used to render Modal dialog window with customizable content and parameterized Overlay, etc.. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Modal.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Dialog</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Dialog" target="_blank">Nuget</a> </p> @@ -86,8 +88,8 @@ </div> <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - Overlay opacity: @(_overlayOpacity/1000) - <input type="range" class="w-100" min="0" max="1000" @bind="_overlayOpacity" @oninput="(e => _overlayOpacity = int.Parse(e.Value?.ToString()))" /> + Overlay opacity: @(_overlayOpacity/100) + <input type="range" class="w-100" min="0" max="100" @bind="_overlayOpacity" @oninput="(e => _overlayOpacity = int.Parse(e.Value?.ToString()))" /> </div> </div> <div class="row pb-2"> @@ -140,7 +142,7 @@ <ModalDialog @ref="_dialog" OverlayBackgroundColor="@_overlayColor" - OverlayOpacity="@(_overlayOpacity/1000)" + OverlayOpacity="@(_overlayOpacity/100)" Height="@_modalHeight" Width="@_modalWitdth" MinHeight="@_modalMinHeight" @@ -205,7 +207,7 @@ //Fully customized dialog private string _overlayColor = "128,128,128"; - private double _overlayOpacity = 500; + private double _overlayOpacity = 50; private double _modalHeight = 270; private double _modalWitdth = 500; private double _modalMinHeight = 100; @@ -229,7 +231,7 @@ } private async Task AcceptDialog() { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog custom content action: 'Ok' Button click: '{nameof(AcceptDialog)}'."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog custom content action: 'Ok' Button click: '{nameof(AcceptDialog)}'."); if(_dialog.IsOpen) { @@ -243,7 +245,7 @@ } private async Task CancelDialog() { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog custom content action: 'Cancel' Button click: '{nameof(CancelDialog)}'."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog custom content action: 'Cancel' Button click: '{nameof(CancelDialog)}'."); _yourName = "Cancel clicked You have canceled the Dialog"; await _dialog.Close(); @@ -252,38 +254,38 @@ //Dialog events public async Task OnOpen() { - _yourName = null; - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnOpen)}' Dialog opened."); + _yourName = string.Empty; + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnOpen)}' Dialog opened."); await Task.Delay(500); - _modal1Log = WriteLog(_modal1Log, $"--------------- End of event handling ---------------"); + _modal1Log = await WriteLog(_modal1Log, $"--------------- End of event handling ---------------"); } public async Task OnClose() { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnClose)}' Dialog closed."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnClose)}' Dialog closed."); await Task.Delay(500); - _modal1Log = WriteLog(_modal1Log, $"--------------- End of event handling ---------------"); + _modal1Log = await WriteLog(_modal1Log, $"--------------- End of event handling ---------------"); } private async Task OnCloseButtonClicked(MouseEventArgs e) { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnCloseButtonClicked)}', Event args: {e}."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnCloseButtonClicked)}', Event args: {e}."); } private async Task OnOverlayClicked(MouseEventArgs e) { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnOverlayClicked)}', Event args: {e}."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnOverlayClicked)}', Event args: {e}."); } private async Task OnEscapeKeyPress(KeyboardEventArgs e) { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnEscapeKeyPress)}', Event args: {e}."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnEscapeKeyPress)}', Event args: {e}."); } private async Task OnTransitionEnded(TransitionEventArgs[] e) { - _modal1Log = WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnTransitionEnded)}', Event args: {e}."); + _modal1Log = await WriteLog(_modal1Log, $"Modal dialog event: '{nameof(OnTransitionEnded)}', Event args: {e}."); } - private string WriteLog(string log, string message) + private async Task<string> WriteLog(string log, string message) { log += $"{DateTime.Now.TimeOfDay}: {message}. \r\n"; - log1.ScrollToEndAsync(); + await log1.ScrollToEndAsync(); return log; } diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/GdprConsents.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/GdprConsents.razor new file mode 100644 index 00000000..60af3f3f --- /dev/null +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/GdprConsents.razor @@ -0,0 +1,256 @@ +<PageScroll /> + +<h1>GDPR Consents</h1> +<p> + Blazor <strong>injectable <code>IGdprConsentService</code> service</strong> and components that renders a customizable GDPR consent Banner or Popup witch Accept/Reject for cookie settings chosen value is persisted to Browser storage. + To initialize GDPR Consents use <code>GdprBanner</code> or <code>GdprModal</code> <strong>only once</strong> in your Blazor App <code>MainLayout.razor</code> page or any common place. + For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/GdprConsent.md" target="_blank">Github</a>. + <br /><strong>Majorsoft.Blazor.Components.GdprConsent</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.GdprConsent" target="_blank">Nuget</a> +</p> + +<div class="container-fluid p-3 mb-3 border rounded"> + <strong>GDPR Component type:</strong> (only <strong>one of the GDPR components should be used once</strong> by apply it on a + <strong>common place in your App e.g.: MainLayout.razor</strong>!) + <select class="form-control selectpicker w-25" @bind="_gdprControlType"> + <option value="Banner">Banner</option> + <option value="Modal">Modal</option> + </select> +</div> + +@if (_gdprControlType == "Banner") +{ + <div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="gdpr-banner" IconActions="PermaLinkIconActions.Copy | PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>GDPR Consent Banner</h3></Content> + </PermaLinkElement> + <p>Renders a small Overlay layer at the bottom of the page with customizable content for showing the given GDPR message.</p> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Banner color (Name, RGB, Hex, HSL): <input class="form-control w-100" @bind="_bannerColor" /> + </div> + </div> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Consent valid: @(_bannerConsentValidDays) day(s) + <input type="range" class="w-100" min="1" max="1000" @bind="_bannerConsentValidDays" @oninput="(e => _bannerConsentValidDays = int.Parse(e.Value?.ToString()))" /> + </div> + </div> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Banner opacity: @(_bannerOpacity / 100) + <input type="range" class="w-100" min="0" max="100" @bind="_bannerOpacity" @oninput="(e => _bannerOpacity = int.Parse(e.Value?.ToString()))" /> + </div> + </div> + </div> + + @*THIS should be in a common part of your App e.g.: MainLayout.razor!!!*@ + <div> + <style> + .gdpr-banner { + font-size: medium; + font-weight: 500; + padding: 16px 0; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + /*border: 1px solid #e0e0e0;*/ + } + </style> + <GdprBanner @ref="_gdprBanner" + BannerOpacity="@(_bannerOpacity / 100)" + BannerBackgroundColor="@_bannerColor" + AnswerValidUntil="@DateTime.Now.AddDays(_bannerConsentValidDays)" + ConsentDetails="@_gdprConsents"> + <Content> + <div class="gdpr-banner"> + <span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> + <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent Banner usage in your Blazor Application.</strong> + + <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprBanner.AcceptAll()">I agree</button> + <button type="button" class="btn btn-secondary m-1" @onclick="async () => await _gdprBanner.RejectAll()">Disagree</button> + + <button type="button" class="btn btn-warning m-1 ml-4" @onclick='() => { _gdprControlType = "Modal"; }'>Customize</button> + </div> + </Content> + </GdprBanner> + </div> +} +else if (_gdprControlType == "Modal") +{ + <div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="gdpr-modal" IconActions="PermaLinkIconActions.Copy | PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>GDPR Consent Modal</h3></Content> + </PermaLinkElement> + <p>Renders a Modal dialog with Overlay layer for the whole page with customizable content for showing the given GDPR message.</p> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Overlay color (Name, RGB, Hex, HSL): <input class="form-control w-100" @bind="_overlayColor" /> + </div> + </div> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Consent valid: @(_bannerConsentValidDays) day(s) + <input type="range" class="w-100" min="1" max="1000" @bind="_bannerConsentValidDays" @oninput="(e => _bannerConsentValidDays = int.Parse(e.Value?.ToString()))" /> + </div> + </div> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Overlay opacity: @(_overlayOpacity / 100) + <input type="range" class="w-100" min="0" max="100" @bind="_overlayOpacity" @oninput="(e => _overlayOpacity = int.Parse(e.Value?.ToString()))" /> + </div> + </div> + </div> + + @*THIS should be in a common part of your App e.g.: MainLayout.razor!!!*@ + <GdprModal @ref="_gdprModal" + OverlayBackgroundColor="@_overlayColor" + OverlayOpacity="@(_overlayOpacity /100)" + ConsentDetails="@_gdprConsents" + Centered="true"> + <Content> + <div class="container-fluid p-3 mb-3"> + <div class="row"> + <div class="col-12"> + <h2 class="" style="justify-content: center;"><span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> Cookie Consent</h2> + </div> + </div> + <div class="row mt-3 mb-4"> + <div class="col-12"> + <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent Modal usage in your Blazor Application.</strong> + </div> + </div> + <div class="row mb-2"> + <div class="col-10"> + Required Cookies: + </div> + <div class="col-2"> + <ToggleSwitch Checked="_gdprConsents[0].IsAccepted" Disabled="true" Width="60" Height="25" /> + </div> + <hr /> + </div> + <div class="row mb-1"> + <div class="col-10"> + Session all Cookies: + </div> + <div class="col-2"> + <ToggleSwitch @bind-Checked="_gdprConsents[1].IsAccepted" @bind-Checked:event="OnToggleChanged" Width="60" Height="25" /> + </div> + </div> + <div class="row mb-1"> + <div class="col-10"> + Tracking all Cookies: + </div> + <div class="col-2"> + <ToggleSwitch @bind-Checked="_gdprConsents[2].IsAccepted" @bind-Checked:event="OnToggleChanged" Width="60" Height="25" /> + </div> + </div> + </div> + </Content> + <Footer> + <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprModal.SaveChoice()">Confirm my choice</button> + <button type="button" class="btn btn-secondary m-1" + @onclick="async () => { _gdprConsents.ForEach(f => f.IsAccepted = true); await _gdprModal.SaveChoice(); }">Accept all</button> + </Footer> + </GdprModal> +} + +<div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="gdpr-consent" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>GDPR Consent data</h3></Content> + </PermaLinkElement> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + @if (_gdprConsentData != null) + { + <label>Consent was answered at: <strong>@_gdprConsentData.AnsweredAt</strong> valid until: <strong>@_gdprConsentData.AnswerValidUntil</strong></label> + if (_gdprConsentData.IsValid) + { + <label>Consent is: <strong>Valid</strong></label> + } + else + { + <label class="text-danger">Consent is: <strong>Invalid</strong></label> + } + + <table class="table table-striped"> + <tr> + <th>Cookie Consent Name</th> + <th>IsAccepted</th> + </tr> + @foreach (var item in _gdprConsentData.GdprConsentDetails) + { + <tr> + <td>@item.ConsentName</td> + <td>@item.IsAccepted</td> + </tr> + } + </table> + + <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprConsentService.ClearGdprConsentDataAsync()">Clear GDPR Consent</button> + } + else + { + <strong class="text-danger">GDPR Consents not yet accepted or rejected...</strong> + } + </div> + </div> +</div> + +@inject IGdprConsentService _gdprConsentService + +@implements IDisposable + +@code { + private GdprConsentData _gdprConsentData; + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + _gdprConsentService.ConsentNotificationService.GdprConsentStateChanged += OnConsentChanged; //Subscribe to change event + + await OnConsentChanged(); //Initial check + } + } + + private string _gdprControlType = "Banner"; + private List<GdprConsentDetail> _gdprConsents; + + protected override void OnInitialized() + { + _gdprConsents = new List<GdprConsentDetail>() + { + new GdprConsentDetail() { ConsentName = "Required", IsAccepted = true }, + new GdprConsentDetail() { ConsentName = "Session", IsAccepted = true }, + new GdprConsentDetail() { ConsentName = "Tracking", IsAccepted = true }, + }; + } + + //GDPR banner + private GdprBanner _gdprBanner; + private string _bannerColor = "lightblue"; + private int _bannerConsentValidDays = 20; + private double _bannerOpacity = 90; + + //GDPR popup + private GdprModal _gdprModal; + private string _overlayColor = "lightgray"; + private double _overlayOpacity = 70; + + private async Task OnConsentChanged() + { + _gdprConsentData = await _gdprConsentService.GetGdprConsentDataAsync(); + StateHasChanged(); + } + + public void Dispose() + { + _gdprConsentService.ConsentNotificationService.GdprConsentStateChanged -= OnConsentChanged; //Unsubscribe from change event + } +} \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Index.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Index.razor index 28374820..cb18d990 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Index.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Index.razor @@ -1,4 +1,6 @@ -<h1>ASP.NET Blazor Components</h1> +<PageScroll /> + +<h1>ASP.NET Blazor Components</h1> <p> <strong>Blazor Components</strong> is a set of <strong>UI Components</strong> and other useful <strong>Extensions</strong> for <a href="https://blazor.net/" target="_blank">Blazor</a> applications. @@ -30,6 +32,7 @@ <ul> <li><NavLink href="debounceinput">Debounce Inputs</NavLink></li> <li><NavLink href="typeahead">Typeahead Inputs</NavLink></li> + <li><NavLink href="maxLengthInput">Max allowed length with Counter Inputs</NavLink></li> <li><NavLink href="timer">Timer Component</NavLink></li> <li><NavLink href="loading">Loading Components</NavLink></li> <li> @@ -43,7 +46,10 @@ <li><NavLink href="jsinterop#resize-js">Resize Js</NavLink></li> <li><NavLink href="jsinterop#clipboard-js">Clipboard Js</NavLink></li> <li><NavLink href="jsinterop#lang-js">Language Js</NavLink></li> + <li><NavLink href="jsinterop#date-js">Browser Date Js</NavLink></li> + <li><NavLink href="jsinterop#theme-js">Browser Theme Js</NavLink></li> <li><NavLink href="jsinterop#geo-js">Geo Js</NavLink></li> + <li><NavLink href="jsinterop#head-js">Head Js</NavLink></li> </ul> </li> <li> @@ -85,6 +91,8 @@ <li><NavLink href="collapse#accordion">Accordion</NavLink></li> </ul> </li> + <li><NavLink href="gdpr">GDPR Consents</NavLink></li> + @*<li><NavLink href="draganddrop">Drag and Drop</NavLink></li> <li><NavLink href="colorpicker">Color Picker</NavLink></li>*@ </ul> diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JSInterop.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JSInterop.razor index a08f189f..ddabac18 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JSInterop.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JSInterop.razor @@ -14,9 +14,11 @@ <br /><strong>Majorsoft.Blazor.Components.Common.JsInterop</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Common.JsInterop" target="_blank">Nuget</a> </p> -@*Later add scroll components here*@ <hr /> -<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageEndAsync())">Scroll to Page bottom <i class="fas fa-arrow-down"></i></button> +@*Scroll to page end with ScrollHandler for custom floating component see ScrollToPageBottom*@ +<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageEndAsync(_smoothScroll))">Scroll to Page bottom <i class="fas fa-arrow-down"></i> (See floating components)</button> +Smooth scroll: +<input type="checkbox" @bind="_smoothScroll" /> <div> <h3>JS Interop features:</h3> @@ -29,6 +31,8 @@ <li><NavLink href="jsinterop#resize-js">Resize Js</NavLink></li> <li><NavLink href="jsinterop#clipboard-js">Clipboard Js</NavLink></li> <li><NavLink href="jsinterop#lang-js">Browser Language Js</NavLink></li> + <li><NavLink href="jsinterop#date-js">Browser Date Js</NavLink></li> + <li><NavLink href="jsinterop#theme-js">Browser Theme Js</NavLink></li> <li><NavLink href="jsinterop#geo-js">Geolocation Js</NavLink></li> <li><NavLink href="jsinterop#head-js">Head Js</NavLink></li> </ul> @@ -50,13 +54,19 @@ <LangJs /> +<BrowserDateJs /> + +<BrowserThemeJs /> + <GeoJs /> <HeadJs /> -@*Later add scroll components here*@ <hr /> -<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageTopAsync())">Scroll to Page top <i class="fas fa-arrow-up"></i></button> +@*Scroll to page end with ScrollHandler for custom floating component see ScrollToPageTop*@ +<button class="btn btn-link" @onclick="@(async () => await _scrollHandler.ScrollToPageTopAsync(_smoothScroll))">Scroll to Page top <i class="fas fa-arrow-up"></i> (See floating components)</button> +Smooth scroll: +<input type="checkbox" @bind="_smoothScroll" /> @implements IAsyncDisposable @inject IScrollHandler _scrollHandler @@ -69,4 +79,6 @@ await _scrollHandler.DisposeAsync(); } } + + private bool _smoothScroll = true; } \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/BrowserDateJs.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/BrowserDateJs.razor new file mode 100644 index 00000000..038dc059 --- /dev/null +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/BrowserDateJs.razor @@ -0,0 +1,24 @@ +<div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="date-js" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>Browser Date JS</h3></Content> + </PermaLinkElement> + + <strong>Browser Date JS</strong> is an injectable <code>IBrowserDateService</code> service simple JS call to <code>new Date();</code> to retrieve client machine date and time. + + <AdvancedTimer IsEnabled="true" IntervalInMilisec="200" Occurring="Times.Infinite()" AutoStart="true" OnIntervalElapsed="@Clock" /> + <p> + <span>Browser date time: <strong>@_date</strong></span> + </p> + +</div> + +@inject IBrowserDateService _dateService + +@code { + private System.DateTime _date; + + private async Task Clock() + { + _date = await _dateService.GetBrowserDateTimeAsync(); + } +} \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/BrowserThemeJs.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/BrowserThemeJs.razor new file mode 100644 index 00000000..fb97d4c7 --- /dev/null +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/BrowserThemeJs.razor @@ -0,0 +1,58 @@ +<div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="theme-js" IconActions="PermaLinkIconActions.Copy | PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>Browser Theme JS</h3></Content> + </PermaLinkElement> + + <strong>Browser Theme JS</strong> is an injectable <code>IBrowserThemeService</code> to handle Browser color scheme queries and changes. + <p> + <span class="@(_theme == BrowserColorThemes.Dark ? "text-dark bg-light" : "text-light bg-dark")">Browser color theme is: <strong>@_theme.ToString()</strong></span> + </p> + + <div class="row pb-2"> + <div class="col-12"> + <button class="btn btn-primary" @onclick="ThemeEventHandler">@(_themeSubscribed ? "Unsubscribe from Browser theme Change" : "Subscribe to Browser theme Change")</button> + </div> + </div> + +</div> + +@implements IAsyncDisposable +@inject IBrowserThemeService _themeService + +@code { + private BrowserColorThemes _theme; + private string _themeEventId; + private bool _themeSubscribed; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + _theme = await _themeService.GetBrowserColorThemeAsync(); + StateHasChanged(); + } + } + + + private async Task ThemeEventHandler() + { + if (_themeSubscribed) + { + await _themeService.RemoveColorThemeChangeAsync(_themeEventId); + } + else + { + _themeEventId = await _themeService.RegisterColorThemeChangeAsync(async (val) => { _theme = val; }); + } + + _themeSubscribed = !_themeSubscribed; + } + + public async ValueTask DisposeAsync() + { + if (_themeService is not null) + { + await _themeService.DisposeAsync(); + } + } +} \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/LangJs.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/LangJs.razor index d033841e..c4b64816 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/LangJs.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/LangJs.razor @@ -20,9 +20,13 @@ @inject ILanguageService _languageService; @code { - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + _detectedBrowserLang = await _languageService.GetBrowserLanguageAsync(); + StateHasChanged(); } //Broswer lang diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/MouseJs.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/MouseJs.razor index 886f1992..3684f2d9 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/MouseJs.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/MouseJs.razor @@ -25,8 +25,11 @@ @inject IGlobalMouseEventHandler _globalMouseEventHandler; @code { - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + await GlobalClickEventHandler(); } diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/ResizeJs.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/ResizeJs.razor index fb3f6fda..3611eb52 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/ResizeJs.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/ResizeJs.razor @@ -33,8 +33,11 @@ @code { private string _resizeEventId = null; - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + await ResizeEventHandler(); _pageSize = await _resizeHandler.GetPageSizeAsync(); } diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/ScrollJs.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/ScrollJs.razor index 2a6ebd63..2eaf9dc5 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/ScrollJs.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/JsDemo/ScrollJs.razor @@ -1,4 +1,32 @@ -<div class="container-fluid p-3 mb-3 border rounded"> +@*Scroll on page components, can be placed anywhere on a page, moved to a new component, etc. but should be used once per page*@ +<ScrollToPageBottom PaddingFromTop="@_scrcollTopBottonMargin" + PaddingFromSide="@_scrcollTopSideMargin" + HorizontalPosition="@_scrollHorizontalPosition" + SmootScroll="@_smoothScroll" + AnimateOnHover="@_animateOnHover" + VisibleFromPagePercentage="2" + VisibleUntilPagePercentage="75"> + <Content> + <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> + <i class="fas fa-2x fa-chevron-down p-1"></i> + </div> + </Content> +</ScrollToPageBottom> + +<ScrollToPageTop PaddingFromBottom="@_scrcollTopBottonMargin" + PaddingFromSide="@_scrcollTopSideMargin" + HorizontalPosition="@_scrollHorizontalPosition" + SmootScroll="@_smoothScroll" + AnimateOnHover="@_animateOnHover" + VisibleFromPagePercentage="30"> + <Content> + <div style="background:dodgerblue; color:white; width: 36px; height:40px; box-shadow: 1px 2px #e1dddd;"> + <i class="fas fa-2x fa-chevron-up p-1"></i> + </div> + </Content> +</ScrollToPageTop> + +<div class="container-fluid p-3 mb-3 border rounded"> <PermaLinkElement PermaLinkName="scroll-js" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> <Content><h3>Scroll JS</h3></Content> </PermaLinkElement> @@ -17,6 +45,48 @@ <hr /> + <p> + <label> + <strong><code>ScrollToPageBottom</code> and <code>ScrollToPageTop</code></strong> components will render "floating" element with customizable placing and content + for wrapping <strong>Scroll JS</strong> scroll to page top or bottom functions. + <strong>Both component can be set inside ANY components which will apply for the whole page. Hence both components should be added only once per page!</strong> + </label> + </p> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Top/Bottom margin (can be set separately for both types of controls): @(_scrcollTopBottonMargin)px + <input type="range" class="w-100" min="0" max="100" @bind="_scrcollTopBottonMargin" @oninput="(e => _scrcollTopBottonMargin = int.Parse(e.Value?.ToString()))" /> + </div> + </div> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Side right/left margin (can be set separately for both types of controls): @(_scrcollTopSideMargin)px + <input type="range" class="w-100" min="0" max="100" @bind="_scrcollTopSideMargin" @oninput="(e => _scrcollTopSideMargin = int.Parse(e.Value?.ToString()))" /> + </div> + </div> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Smooth scroll (can be set separately for both types of controls): <input class="mr-3" type="checkbox" @bind="_smoothScroll" /> + Animate on Hover (can be set separately for both types of controls): <input type="checkbox" @bind="_animateOnHover" /> + </div> + </div> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + Horizontal Position right/left (can be set separately for both types of controls): <select class="form-control selectpicker w-100" @bind="_scrollHorizontalPosition"> + @foreach (var item in Enum.GetValues(typeof(PageScrollHorizontalPosition))) + { + <option value="@item">@item</option> + } + </select> + </div> + </div> + + <hr /> + <div class="row pb-2"> <div class="col-12"> <p><code>RegisterPageScrollAsync()</code> will add event listener to HTML document/window <code>scroll</code></p> @@ -47,8 +117,11 @@ @code { private string _scrollEventId = null; - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + await ScrollEventHandler(); } @@ -84,6 +157,14 @@ _scrollSubscribed = !_scrollSubscribed; } + //Page scroll elements + private int _scrcollTopBottonMargin = 20; + private int _scrcollTopSideMargin = 24; + private byte _scrcollBefore = 5; + private PageScrollHorizontalPosition _scrollHorizontalPosition = PageScrollHorizontalPosition.Right; + private bool _smoothScroll = true; + private bool _animateOnHover = true; + public async ValueTask DisposeAsync() { if (_scrollHandler is not null) diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Loading.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Loading.razor index 3751c083..a868c345 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Loading.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Loading.razor @@ -1,8 +1,9 @@ @using System.ComponentModel.DataAnnotations; +<PageScroll /> <h1>Loading Components</h1> <p> - Blazor components that renders Overlay for page load. HTML @("<button>") with customizable content for showing async operation in progress/loading state. For usege see soruce code and docs on + Blazor components that renders Overlay for page load. HTML @("<button>") with customizable content for showing async operation in progress/loading state. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Loading.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Loading</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Loading" target="_blank">Nuget</a> </p> @@ -27,8 +28,8 @@ </div> <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - Overlay opacity: @(_overlayOpacity/1000) - <input type="range" class="w-100" min="0" max="1000" @bind="_overlayOpacity" @oninput="(e => _overlayOpacity = int.Parse(e.Value?.ToString()))" /> + Overlay opacity: @(_overlayOpacity/100) + <input type="range" class="w-100" min="0" max="100" @bind="_overlayOpacity" @oninput="(e => _overlayOpacity = int.Parse(e.Value?.ToString()))" /> </div> </div> <div class="row pb-2"> @@ -37,7 +38,7 @@ </div> </div> - <LoadingPage IsLoading="@_pageIsLoading" OverlayBackgroundColor="@_overlayColor" OverlayOpacity="@(_overlayOpacity/1000)"> + <LoadingPage IsLoading="@_pageIsLoading" OverlayBackgroundColor="@_overlayColor" OverlayOpacity="@(_overlayOpacity / 100)"> <LoadingContent> @((MarkupString)_overlayContent) </LoadingContent> @@ -59,8 +60,8 @@ </div> <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> - Overlay opacity: @(_overlayOpacityTable / 1000) - <input type="range" class="w-100" min="0" max="1000" @bind="_overlayOpacityTable" @oninput="(e => _overlayOpacityTable = int.Parse(e.Value?.ToString()))" /> + Overlay opacity: @(_overlayOpacityTable / 100) + <input type="range" class="w-100" min="0" max="100" @bind="_overlayOpacityTable" @oninput="(e => _overlayOpacityTable = int.Parse(e.Value?.ToString()))" /> </div> </div> <div class="row pb-2"> @@ -69,7 +70,7 @@ </div> </div> - <LoadingElement IsLoading="@_elementIsLoading" OverlayBackgroundColor="@_overlayColorTable" OverlayOpacity="@(_overlayOpacityTable/1000)"> + <LoadingElement IsLoading="@_elementIsLoading" OverlayBackgroundColor="@_overlayColorTable" OverlayOpacity="@(_overlayOpacityTable / 100)"> <LoadingContent> @((MarkupString)_overlayContent2) </LoadingContent> @@ -185,7 +186,7 @@ //Page Loading private string _overlayColor = "lightblue"; - private double _overlayOpacity = 500; + private double _overlayOpacity = 50; private string _overlayContent = @"<i class=""fa fa-cog fa-3x fa-spin""></i> <h2 class=""m-3"">Refreshing...</h2>"; @@ -207,7 +208,7 @@ //Element Loading private string _overlayColorTable = "orange"; - private double _overlayOpacityTable = 500; + private double _overlayOpacityTable = 50; private bool _elementIsLoading = false; private string _overlayContent2 = @"<i class=""fa fa-cog fa-3x fa-spin""></i> <h2 class=""m-3"">Refreshing...</h2>"; diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Logger.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Logger.razor index 84376f94..87e142ba 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Logger.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Logger.razor @@ -8,11 +8,11 @@ Blazor Extensions are providing useful features to develop Balazor applications: <ul> <li> - Blazor.Server.Logging.Console: Enables <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/ServerHostedLogging.md" target="_blank">browser conole logging</a> for Blazor applications using <strong>Server Hosted model</strong>. + Blazor.Server.Logging.Console: Enables <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/ServerHostedLogging.md" target="_blank">browser console logging</a> for Blazor applications using <strong>Server Hosted model</strong>. <br /><strong>Majorsoft.Blazor.Server.Logging.Console</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Server.Logging.Console" target="_blank">Nuget</a> </li> <li> - Blazor.WebAssembly.Logging.Console: Enables <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/WebAssemblyHostedLogging.md" target="_blank">browser conole logging</a> for Blazor applications using <strong>WebAssembly Hosting model</strong>. + Blazor.WebAssembly.Logging.Console: Enables <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/WebAssemblyHostedLogging.md" target="_blank">browser console logging</a> for Blazor applications using <strong>WebAssembly Hosting model</strong>. <br /><strong>Majorsoft.Blazor.WebAssembly.Logging.Console</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.WebAssembly.Logging.Console" target="_blank">Nuget</a> </li> </ul> diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Maps.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Maps.razor index f1c14b66..8e487a44 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Maps.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Maps.razor @@ -20,6 +20,8 @@ </ul> </div> +<PageScroll /> + <MapsGoogle /> <MapsBing /> diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/MapsGoogle.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/MapsGoogle.razor index 09077813..1f97aa32 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/MapsGoogle.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/MapsGoogle.razor @@ -312,7 +312,10 @@ OnMapIdle="@OnMapIdle" ApiKey="@_googleMapsApiKey" /> - @*<GoogleMap Center="@_jsMapCenter" ApiKey="@_googleMapsApiKey" />*@ + @*<GoogleMap @bind-Center="_jsMapCenter" @bind-Center:event="OnMapCenterChanged" + @bind-Zoom="_jsMapZoomLevel" @bind-Zoom:event="OnMapZoomLevelChanged" + OnMapInitialized="@(() => {})" + ApiKey="@_googleMapsApiKey" />*@ </div> </div> @@ -352,8 +355,11 @@ @code { private string _googleMapsApiKey = "AIzaSyAv-6SailPQN1R5PytUAkbdaGI9IHZTU5s"; - protected override void OnInitialized() + protected override async Task OnAfterRenderAsync(bool firstRender) { + if (!firstRender) + return; + //Static map _staticMapMarkers.ElementAt(0).Locations.Add(new GeolocationData(1.111, 2.222)); _staticMapMarkers.ElementAt(1).Locations.Add(new GeolocationData(17.111, 33.222)); @@ -692,7 +698,7 @@ } private async Task OnMapZoomLevelChanged(byte zoom) { - //Can be used with Binding and custom event: @bind-ZoomLevel="_jsMapZoomLevel" @bind-ZoomLevel:event="OnMapZoomLevelChanged" + //Can be used with Binding and custom event: @bind-Zoom="_jsMapZoomLevel" @bind-Zoom:event="OnMapZoomLevelChanged" if (_logOtherEvents) { _mapsLog = await WriteLog(_mapsLog, $"Map Zoom level changed to: {zoom}"); diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/MaxLengthInputs.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/MaxLengthInputs.razor new file mode 100644 index 00000000..aaaa4811 --- /dev/null +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/MaxLengthInputs.razor @@ -0,0 +1,197 @@ +<style> + .countDownText { + font-size: small; + color: gray; + } + .countDownText.red { + color: red; + } +</style> + +<PageScroll /> + +<h1>Max allowed length Input controls with Counter</h1> +<p> + Blazor components that renders an Input, InputText, Textarea or InputTextarea, etc. element with <code>maxlength</code> set and counter to show remaining characters. For usage see source code and docs on + <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Inputs.md" target="_blank">Github</a>. + <br /><strong>Majorsoft.Blazor.Components.Inputs</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Inputs" target="_blank">Nuget</a> +</p> + + +<div class="container-fluid p-3 mb-3 border rounded"> + <h3>MaxLengthInput component</h3> + <p>Wraps around <strong>HTML Input</strong> control and sets <code>maxlength</code> property with notification onChange.</p> + + <div> + <div> + <input type="range" min="0" max="50" @bind="_maxLengthInputAllowed" @oninput="(e => _maxLengthInputAllowed = int.Parse(e.Value?.ToString()))" /> <pre style="display: inline;">Max allowed chars: @_maxLengthInputAllowed</pre> + </div> + </div> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + <MaxLengthInput id="maxInput1" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthInputAllowed} char(s)")" + @bind-Value="@_maxLengthInputValue" @bind-Value:event="OnInput" + MaxAllowedChars="@_maxLengthInputAllowed" + CountdownTextClass="@_maxLengthInputTextClass" + OnRemainingCharsChanged="MaxLengthInputRemainingCharsChanged" /> + </div> + </div> + + <div>Actual value: @_maxLengthInputValue</div> +</div> + +<div class="container-fluid p-3 mb-3 border rounded"> + <h3>MaxLengthInputText component</h3> + <p>Wraps around <strong>Blazor InputText</strong> control and sets <code>maxlength</code> property with notification onChange.</p> + + <div> + <div> + <input type="range" min="0" max="50" @bind="_maxLengthInputTextAllowed" @oninput="(e => _maxLengthInputTextAllowed = int.Parse(e.Value?.ToString()))" /> <pre style="display: inline;">Max allowed chars: @_maxLengthInputTextAllowed</pre> + </div> + </div> + + <EditForm Model="@exampleModel"> + <DataAnnotationsValidator /> + <ValidationSummary /> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + <MaxLengthInputText id="maxInput2" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthInputTextAllowed} char(s)")" + @bind-Value="exampleModel.Name" + MaxAllowedChars="@_maxLengthInputTextAllowed" + CountdownTextClass="@_maxLengthInputTextTextClass" + OnRemainingCharsChanged="MaxLengthInputTextRemainingCharsChanged" /> + </div> + </div> + <div class="pb-2"> + <button class="btn btn-primary" type="submit">Submit</button> + </div> + </EditForm> + + <div>Actual value: @(exampleModel.Name)</div> +</div> + + +<div class="container-fluid p-3 mb-3 border rounded"> + <h3>MaxLengthTextarea component</h3> + <p>Wraps around <strong>HTML TextArea</strong> control and sets <code>maxlength</code> property with notification onChange.</p> + + <div> + <div> + <input type="range" min="0" max="100" @bind="_maxLengthTextareaAllowed" @oninput="(e => _maxLengthTextareaAllowed = int.Parse(e.Value?.ToString()))" /> <pre style="display: inline;">Max allowed chars: @_maxLengthTextareaAllowed</pre> + </div> + </div> + + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + <MaxLengthTextarea id="maxTextArea1" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthTextareaAllowed} char(s)")" + @bind-Value="@_maxLengthTextareaValue" @bind-Value:event="OnInput" + MaxAllowedChars="@_maxLengthTextareaAllowed" + CountdownTextClass="@_maxLengthTextareaTextClass" + OnRemainingCharsChanged="MaxLengthTextareaRemainingCharsChanged" /> + </div> + </div> + + <div>Actual value: @_maxLengthTextareaValue</div> +</div> + +<div class="container-fluid p-3 mb-3 border rounded"> + <h3>MaxLengthInputTextArea component</h3> + <p>Wraps around <strong>Blazor InputTextArea</strong> control and sets <code>maxlength</code> property with notification onChange.</p> + + <div> + <div> + <input type="range" min="0" max="50" @bind="_maxLengthInputTextAreaAllowed" @oninput="(e => _maxLengthInputTextAllowed = int.Parse(e.Value?.ToString()))" /> <pre style="display: inline;">Max allowed chars: @_maxLengthInputTextAreaAllowed</pre> + </div> + </div> + + <EditForm Model="@exampleModel2"> + <DataAnnotationsValidator /> + <ValidationSummary /> + <div class="row pb-2"> + <div class="col-12 col-lg-8 col-xl-5"> + <MaxLengthInputTextArea id="maxTextArea2" class="form-control w-100" placeholder="@($"You can type maximum: {_maxLengthInputTextAreaAllowed} char(s)")" + @bind-Value="exampleModel2.Name" + MaxAllowedChars="@_maxLengthInputTextAreaAllowed" + CountdownTextClass="@_maxLengthInputTextAreaClass" + OnRemainingCharsChanged="MaxLengthInputTextAreaRemainingCharsChanged" /> + </div> + </div> + <div class="pb-2"> + <button class="btn btn-primary" type="submit">Submit</button> + </div> + </EditForm> + + <div>Actual value: @(exampleModel2.Name)</div> +</div> + +@code { + //MaxLengthInput + private int _maxLengthInputAllowed = 10; + private string _maxLengthInputValue = ""; + private string _maxLengthInputTextClass = "countDownText"; + + private void MaxLengthInputRemainingCharsChanged(int remainingChars) + { + _maxLengthInputTextClass = "countDownText"; + + if (_maxLengthInputAllowed * 0.2 >= remainingChars) + { + _maxLengthInputTextClass = "countDownText red"; + } + } + + //MaxLengthInputText + private int _maxLengthInputTextAllowed = 10; + private string _maxLengthInputTextTextClass = "countDownText"; + + private void MaxLengthInputTextRemainingCharsChanged(int remainingChars) + { + _maxLengthInputTextTextClass = "countDownText"; + + if (_maxLengthInputTextAllowed * 0.2 >= remainingChars) + { + _maxLengthInputTextTextClass = "countDownText red"; + } + } + + + //MaxLengthTextarea + private int _maxLengthTextareaAllowed = 20; + private string _maxLengthTextareaValue = ""; + private string _maxLengthTextareaTextClass = "countDownText"; + + private void MaxLengthTextareaRemainingCharsChanged(int remainingChars) + { + _maxLengthTextareaTextClass = "countDownText"; + + if (_maxLengthTextareaAllowed * 0.2 >= remainingChars) + { + _maxLengthTextareaTextClass = "countDownText red"; + } + } + + //MaxLengthInputTextArea + private int _maxLengthInputTextAreaAllowed = 20; + private string _maxLengthInputTextAreaClass = "countDownText"; + + private void MaxLengthInputTextAreaRemainingCharsChanged(int remainingChars) + { + _maxLengthInputTextAreaClass = "countDownText"; + + if (_maxLengthInputTextAreaAllowed * 0.2 >= remainingChars) + { + _maxLengthInputTextAreaClass = "countDownText red"; + } + } + + //Form model + private ExampleModel exampleModel = new ExampleModel() { Name = "" }; + private ExampleModel exampleModel2 = new ExampleModel(); + public class ExampleModel + { + [Required] + [StringLength(10, ErrorMessage = "Name is too long.")] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Permalink.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Permalink.razor index 9d9988bf..6b8a71e9 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Permalink.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Permalink.razor @@ -3,12 +3,14 @@ textarea { height: 150px !important; } </style> +<PageScroll /> <h1>Permalink (#link) extension and component</h1> <p> - Blazor <strong>injectable <code>IPermaLinkWatcherService</code> service</strong> and <code>PermaLinkElement</code> wrapper component which allows navigation inside Blazor pages (#permalink). - For usege see soruce code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/PermaLink.md" target="_blank">Github</a>. + Blazor <strong>injectable <code>IPermaLinkWatcherService</code> service</strong> and <code>PermaLinkElement</code> wrapper component which allows navigation inside Blazor pages (#permalink). + To initialize navigation watcher use <code>PermalinkBlazorWasmInitializer</code> or <code>PermaLinkBlazorServerInitializer</code> <strong>only once</strong> in your Blazor App <code>MainLayout.razor</code> page or any common place. + For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/PermaLink.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.PermaLink</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.PermaLink" target="_blank">Nuget</a> </p> diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/SiteAnalytics.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/SiteAnalytics.razor new file mode 100644 index 00000000..c1e24e34 --- /dev/null +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/SiteAnalytics.razor @@ -0,0 +1,83 @@ +<PageScroll /> + +<div class="container-fluid p-3 mb-3 border rounded"> + <h1>Website Analytics extensions</h1> + <p> + Blazor extension that enables analytics services usage for Blazor applications e.g. Google Analytics, etc. For usage see source code and docs on + <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Analytics.md" target="_blank">Github</a>. + <br /><strong>Majorsoft.Blazor.Extensions.Analytics</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Extensions.Analytics" target="_blank">Nuget</a> + </p> + + <div> + <h3>Supported Analytics service providers:</h3> + <ul> + <li><NavLink href="analytics#google">Google Analytics</NavLink></li> + </ul> + </div> + + <div class="container-fluid p-3 mb-3 border rounded"> + <PermaLinkElement PermaLinkName="google" IconActions="PermaLinkIconActions.Copy|PermaLinkIconActions.Navigate" IconMarginTop="8" IconSize="18"> + <Content><h3>Google Analytics</h3></Content> + </PermaLinkElement> + <p> + <strong>Google Analytics</strong> is a web analytics service offered by Google that tracks and reports website traffic, etc. inside the Google Marketing Platform. + <br /> + <strong><code>IGoogleAnalyticsService</code> is an injectable service</strong> for enabling + <a href="https://support.google.com/analytics/answer/1008015?hl=en#" target="_blank">Google Analytics</a> page tracking in Blazor Apps. + To make the initialization simple use <code>GoogleAnalyticsInitializer</code> component in your <code>MainLayout.razor</code> page and provide Google Analytics TrackingId. + </p> + + <p> + <strong>To see how Majorsoft.Blazor.Extensions.Analytics works please check the demo code. And Google + <a href="https://developers.google.com/gtagjs/reference/api">analytics features</a> since this extension mostly a JS wrapper for <code>gtag.js</code>. + </strong> + </p> + </div> + +</div> + +@using System.Dynamic +@inject IGoogleAnalyticsService _googleAnalytincsService +@inject ILogger<SiteAnalytics> _logger +@implements IAsyncDisposable + +@code { + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if(firstRender) + { + await _googleAnalytincsService.GetAsync("session_id", async (res) => { _logger.LogInformation($"Google analytics Get result: {res}"); }); + + //Custom set + dynamic exp = new ExpandoObject(); + exp.test = 27; + + await _googleAnalytincsService.SetAsync(exp); + + //Get cutoms set value + await _googleAnalytincsService.GetAsync("test", async (res) => { _logger.LogInformation($"Google analytics Get result: {res}"); }); + + //Built in Search event usage + dynamic exp2 = new ExpandoObject(); + exp2.search_term = "Searching custom event..."; + await _googleAnalytincsService.EventAsync(GoogleAnalyticsEventTypes.search, exp2); + + //Custom event usage + await _googleAnalytincsService.CustomEventAsync("testEvent", new GoogleAnalyticsCustomEventArgs() + { + Action = "Test action", + Category = "Test category", + Label = "Test label", + Value = 1234 + }); + } + } + + public async ValueTask DisposeAsync() + { + if(_googleAnalytincsService is not null) + { + await _googleAnalytincsService.DisposeAsync(); + } + } +} \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Tabs.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Tabs.razor index 223f02aa..c79da20f 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Tabs.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Tabs.razor @@ -1,13 +1,15 @@ -<h1>Tabs Components</h1> +<PageScroll /> + +<h1>Tabs Components</h1> <p> - Blazor component that renders customizable Tabs element panel with many tabs and custom content. For usege see soruce code and docs on + Blazor component that renders customizable Tabs element panel with many tabs and custom content. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Tabs.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Tabs</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Tabs" target="_blank">Nuget</a> </p> <div class="container-fluid p-3 mb-3 border rounded"> <h3>TabsPanel with TabItems</h3> - <p>Renders <strong><code>TabsPanel</code> container </strong> for <strong><code>TabItem</code> components</strong> with custumizable header and content.</p> + <p>Renders <strong><code>TabsPanel</code> container </strong> for <strong><code>TabItem</code> components</strong> with customizable header and content.</p> <div class="row pb-2"> <div class="col-12 col-lg-8 col-xl-5"> diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/TimerComponent.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/TimerComponent.razor index 79fcb3be..4b0ea5cf 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/TimerComponent.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/TimerComponent.razor @@ -1,6 +1,6 @@ <h1>Timer Component</h1> <p> - Blazor component that can be used as a simple scheduler or performing periodically repeated tasks by calling custom async code. For usege see soruce code and docs on + Blazor component that can be used as a simple scheduler or performing periodically repeated tasks by calling custom async code. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Timer.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Timer</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Timer" target="_blank">Nuget</a> </p> diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Toggle.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Toggle.razor index 5522c9c4..e9f11264 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Toggle.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Toggle.razor @@ -1,6 +1,8 @@ -<h1>Toggler Components</h1> +<PageScroll /> + +<h1>Toggler Components</h1> <p> - Blazor component that renders customizable Toggle Switch and Toggle Button components. For usege see soruce code and docs on + Blazor component that renders customizable Toggle Switch and Toggle Button components. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Toggle.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Toggle</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Toggle" target="_blank">Nuget</a> </p> diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Typeahead.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Typeahead.razor index b17480d4..24af10fd 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Components/Typeahead.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Components/Typeahead.razor @@ -3,10 +3,11 @@ textarea { height: 150px !important; } </style> +<PageScroll /> <h1>Typeahead Input controls</h1> <p> - Blazor component that renders an HTML Input or InputText with Typeahead panel. For usege see soruce code and docs on + Blazor component that renders an HTML Input or InputText with Typeahead panel. For usage see source code and docs on <a href="https://github.com/majorimi/blazor-components/blob/master/.github/docs/Typeahead.md" target="_blank">Github</a>. <br /><strong>Majorsoft.Blazor.Components.Typeahead</strong> package is available on <a href="https://www.nuget.org/packages/Majorsoft.Blazor.Components.Typeahead" target="_blank">Nuget</a> </p> diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Majorsoft.Blazor.Components.DemoApp.csproj b/demo/Majorsoft.Blazor.Components.DemoApp/Majorsoft.Blazor.Components.DemoApp.csproj index 8f8dadcc..839d7030 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Majorsoft.Blazor.Components.DemoApp.csproj +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Majorsoft.Blazor.Components.DemoApp.csproj @@ -6,21 +6,24 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Majorsoft.Blazor.Components.Collapse" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Components.Common.JsInterop" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Components.CssEvents" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Components.Debounce" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Components.Loading" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Components.Maps" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Components.Modal" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Components.PermaLink" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Components.Tabs" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Components.Timer" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Components.Toggle" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Components.Typeahead" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Extensions.BrowserStorage" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.Server.Logging.Console" Version="1.3.0" /> - <PackageReference Include="Majorsoft.Blazor.WebAssembly.Logging.Console" Version="1.3.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.Collapse" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.Common.JsInterop" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.CssEvents" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.Debounce" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.GdprConsent" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.Inputs" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.Loading" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.Maps" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.Modal" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.PermaLink" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.Tabs" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.Timer" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.Toggle" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Components.Typeahead" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Extensions.Analytics" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Extensions.BrowserStorage" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.Server.Logging.Console" Version="1.4.0" /> + <PackageReference Include="Majorsoft.Blazor.WebAssembly.Logging.Console" Version="1.4.0" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="5.0.3" PrivateAssets="all" /> <PackageReference Include="System.Net.Http.Json" Version="5.0.0" /> diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Pages/AnalyticsPage.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Pages/AnalyticsPage.razor new file mode 100644 index 00000000..a6cd0ab8 --- /dev/null +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Pages/AnalyticsPage.razor @@ -0,0 +1,3 @@ +@page "/analytics" + +<SiteAnalytics /> \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Pages/GdprConsentPage.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Pages/GdprConsentPage.razor new file mode 100644 index 00000000..92e71ab7 --- /dev/null +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Pages/GdprConsentPage.razor @@ -0,0 +1,3 @@ +@page "/gdpr" + +<GdprConsents /> \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Pages/MaxLengthInputPage.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Pages/MaxLengthInputPage.razor new file mode 100644 index 00000000..47643464 --- /dev/null +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Pages/MaxLengthInputPage.razor @@ -0,0 +1,3 @@ +@page "/maxLengthInput" + +<MaxLengthInputs /> \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Program.cs b/demo/Majorsoft.Blazor.Components.DemoApp/Program.cs index c573fdc8..d4afdcd3 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Program.cs +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Program.cs @@ -12,6 +12,8 @@ using Majorsoft.Blazor.Components.PermaLink; using Majorsoft.Blazor.Components.Maps; using Majorsoft.Blazor.Extensions.BrowserStorage; +using Majorsoft.Blazor.Components.GdprConsent; +using Majorsoft.Blazor.Extensions.Analytics; namespace Majorsoft.Blazor.Components.DemoApp { @@ -30,6 +32,10 @@ public static async Task Main(string[] args) builder.Services.AddMapExtensions(); builder.Services.AddBrowserStorage(); + builder.Services.AddGoogleAnalytics(); + + builder.Services.AddGdprConsent(); + builder.Logging.AddBrowserConsole() .SetMinimumLevel(LogLevel.Debug).AddFilter("Microsoft", LogLevel.Information); diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Shared/MainLayout.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Shared/MainLayout.razor index d50db091..2cc23eec 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Shared/MainLayout.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Shared/MainLayout.razor @@ -16,18 +16,10 @@ </div> </div> -@using Blazor.Components.PermaLink -@inject IPermaLinkWatcherService _permalinkWatcher -@implements IDisposable +@*Permalink initialize*@ +@using Majorsoft.Blazor.Components.PermaLink +<PermalinkBlazorWasmInitializer /> -@code { - protected override void OnInitialized() - { - _permalinkWatcher.WatchPermaLinks(); - } - - public void Dispose() - { - _permalinkWatcher.Dispose(); - } -} \ No newline at end of file +@* Google Analytics initialize*@ +@using Majorsoft.Blazor.Extensions.Analytics.Google +<GoogleAnalyticsInitializer TrackingId="G-1QD2VGTEWX" /> \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Shared/NavMenu.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Shared/NavMenu.razor index 508a93dd..a7ecf29d 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/Shared/NavMenu.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Shared/NavMenu.razor @@ -24,7 +24,10 @@ <NavLink class="nav-link" href="logger">Console logger</NavLink> </li> <li class="nav-item"> - <NavLink class="nav-link" href="browserstorage">Browser strorage</NavLink> + <NavLink class="nav-link" href="browserstorage">Browser storage</NavLink> + </li> + <li class="nav-item"> + <NavLink class="nav-link" href="analytics">Analytics</NavLink> </li> </ul> </div> @@ -39,6 +42,9 @@ <li class="nav-item"> <NavLink class="nav-link" href="typeahead">Typeahead</NavLink> </li> + <li class="nav-item"> + <NavLink class="nav-link" href="maxLengthInput">Max Length</NavLink> + </li> </ul> </div> </li> @@ -92,6 +98,11 @@ <span class="fa fa-lg fa-chevron-circle-down" aria-hidden="true"></span> Collapse </NavLink> </li> + <li class="nav-item px-2"> + <NavLink class="nav-link" href="gdpr"> + <span class="fa fa-lg fa-cookie-bite" aria-hidden="true"></span> GDPR Consents + </NavLink> + </li> <li class="nav-item px-2" hidden="hidden"> <NavLink class="nav-link" href="draganddrop"> <span class="fa fa-lg fa-arrows-alt" aria-hidden="true"></span> Drag and Drop diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/Shared/PageScroll.razor b/demo/Majorsoft.Blazor.Components.DemoApp/Shared/PageScroll.razor new file mode 100644 index 00000000..b58cf3d4 --- /dev/null +++ b/demo/Majorsoft.Blazor.Components.DemoApp/Shared/PageScroll.razor @@ -0,0 +1,16 @@ +@*Scroll on page components wrapped into it's own component and applied on some demo pages*@ +<ScrollToPageBottom VisibleFromPagePercentage="2" VisibleUntilPagePercentage="75"> + <Content> + <div style="background:whitesmoke; border-radius:16px; width: 32px; height:32px; box-shadow: rgb(0 0 0 / 25%) 0px 3px 6px 0px;"> + <i style="padding-left: 6px; padding-top:2px;" class="fas fa-2x fa-angle-down"></i> + </div> + </Content> +</ScrollToPageBottom> + +<ScrollToPageTop VisibleFromPagePercentage="20"> + <Content> + <div style="background:whitesmoke; border-radius:16px; width: 32px; height:32px; box-shadow: rgb(0 0 0 / 25%) 0px 3px 6px 0px;"> + <i style="padding-left: 6px; padding-bottom:2px;" class="fas fa-2x fa-angle-up"></i> + </div> + </Content> +</ScrollToPageTop> \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/_Imports.razor b/demo/Majorsoft.Blazor.Components.DemoApp/_Imports.razor index 6605fd5a..4b553083 100644 --- a/demo/Majorsoft.Blazor.Components.DemoApp/_Imports.razor +++ b/demo/Majorsoft.Blazor.Components.DemoApp/_Imports.razor @@ -6,6 +6,7 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop +@using Microsoft.Extensions.Logging @using System.ComponentModel.DataAnnotations @@ -37,6 +38,8 @@ @using Majorsoft.Blazor.Components.Common.JsInterop.Language @using Majorsoft.Blazor.Components.Common.JsInterop.Geo @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.PermaLink @@ -49,4 +52,10 @@ @using Majorsoft.Blazor.Components.Maps @using Majorsoft.Blazor.Components.Maps.Google -@using Majorsoft.Blazor.Extensions.BrowserStorage \ No newline at end of file +@using Majorsoft.Blazor.Extensions.BrowserStorage + +@using Majorsoft.Blazor.Extensions.Analytics.Google + +@using Majorsoft.Blazor.Components.Inputs + +@using Majorsoft.Blazor.Components.GdprConsent \ No newline at end of file diff --git a/demo/Majorsoft.Blazor.Components.DemoApp/wwwroot/blazor.components.png b/demo/Majorsoft.Blazor.Components.DemoApp/wwwroot/blazor.components.png new file mode 100644 index 0000000000000000000000000000000000000000..bcb777cea0acfe5cac33f9df5ef685f56917047a GIT binary patch literal 8130 zcmX9@1ymK^*B$!N-AK2TbjPE+mF9scAl+S$6!FpB9fGKYbf-R~>(MCP-SF}I{%g(L zIp^$i_daLtT{E-ReALlW#>Jw-0ssJ4RYgJX`MUbw2|{@uxzDmD0RUN6N5eqz*;OOU z(;zN1<4)8kEQ<n$wt?L|WOzKNCkfQI3G6(_8-bbU@gK0m0~9|S<{YWdInT!P@dR8v z0_Q|PwlGkB0PL$#WzzwL;y~rI`;2}Dem^I$0QsrFuM6M^1`G)S<-9=gv;B+;0~L|L z;Qx3`*;D6$jeFqiD=;erl<@&2&nl0=MLO`um^JM==XqiX&_f7hHv{tz!0EH-2Cy#( zls!YSz`!GL_CKBuV4)jW1_2rWi`@Nxd>r`uj7|gpmVxaSV4fE!83op$K%Y7Lf6Pum z+Y@k331oKwh-dc+IP(EI+kr(<pyD~c3G9gg<)T3O9dH^5^gQ=i4NU$|D;5}jF8ByI z2m^XIfZY{fYYEtF1{MZ@wRT{k1z30jZZCo3Ti`SafIm;k1;#>w-e9070vH$pR-=G{ zbKnFY$TVY5eFM}#H`M_wb^?nyz<*U40zaPb!!2+^0AvyZSsTFKvs@xD>;ZH@fJRxM ziW?|-t}Pk(^%|%P1N!rUF=e3U8Jz<DJ`33aEnYw;29PogteVi~nK9(305xmCP8l%8 z2oz`o-%Ww~XBCnEdc1sY6CFqw1j?Ta=mD0)f&MjM_Zm3e1r8R0ZB5|Y5U|z=%!7ey zW1yi3m^cQGZ2sf#;si=@@B6!-_fVy$o|Zm9jJt&Sh>3)ZMtg$4O~iCV!{R@Dx$$^G z&iNp>oc4GxKq<@&r{c=pI}V)`eMCG$wV4hd`Qq+)cOz|DkG|AuDRsHIE&a)EDl2=i zsovl>)_^G+K91VR$OM{=<$rtVNOF7BvWPSr8jyABc1_$r?Yex7N&4=vA0+<8YUZjb z^whL6U0y5H$uJ*Qw+b#;@ot<98177eJF_^`y6@8mA6tU<tu*GOnJRk$08Nalf}DXb zVm~w5W!;2qAf7wm&?_S9Ap2bmL0UMO3sW1GighB%kD#^hXcoUb8yPqhi8v(764}{H zw2#`4V-K9{^&cCgW^2pEDf#UBT|Tsg+#LFR^}yveska+1Qv5{r{|0jcA`q)5_<QH$ z(Y0ADGIZ?yr}(bDzpg_J{S3b=W*rID4w*4(BMabLJfv7R_bXp5G~aEJBFus}v_fp> zIl7P%Bj!p2buZdBPTC^lJ}<#7vl0sJ3zq+UxG)c?hre+AG4vfg{??j`k(&CgNbR5B zCC146{y$@@Z;Dm79v>n;rtA_bj{nt_64~ODIuCF_Y4$U;PqKfDK7=!iz<_oH!L<C^ z*!d;<1372OvHPv(zuYL>4UyEjd!Wu))OagNAZ8OVP@I3{->0OoQx)0rgxFh@Rrf`x z{ooJbkconZ`6{4;vkJo=SuBquLO!2ZYK&LW;&N9+u~$V(Yh5EAdKkp+K%^+)Z?e0N zQPF)DvAfVPXbV;lA+Hn@szG|CU8i>ozT0Q*=bu7240)Dt=y63#{@_(xX4*QWwvq*X zyR`h*Jz0Pz4<g0sC8&jCvo{RCaO<N*6Gmow^WyUhL9<!5=?@snU41?W!y%fqIj(1@ z-pX2)QH4B^KvQ{Z7!eiY(thN?+TlGaH7K8H+(Da`ulFpye1#11#+?&y6!|vsg;KTP zlbz5vM817V0R*PQ7Wna>HOJT5C!Zj<Teb;j>~ohU=`&&y@6ggLN`>B}Vz;QNJzFNv zNyT-rRDA5-7I}FtP1pgUUO7%NMRBW{Boc=N5h%`vTEzC6c&_Vj#|UB--0wB8VNdRe zZRq?rVpz<(-5U!wp(48{v-k}ZE(%`wjVdg^y`Y|+xsJVWZP&{4>o1oqr8g<FEYZW4 zFHG}FEJIeIM?4akj)NlQF1eN=C+qz;rp*M7R_j<=V<+txB_x!_=G?<$1R(4&%QA_; zE3w)0^&9dy(`FIj^q0}5eW_#1P{CJCE#evWadZmb!*pkzlihDmv6D@MEG;YC)db^G zY}OoTc-E*s6W9UJg?L;ItZ&$6LmDUAyv{4TOqN2OjIsS4?I_H$Ey}MOlBN>S@eIC) zb+j-4&TYA0*9LKN9*dnRwqfLc<9p<=S^t7K{#k(3Bw-YadI`N}@XDp^XzLhb(~*T^ z#iI4=;S=>{k%hz)-J#gvO8zkx_sBGAdMr;Lx&UjSD^R^r6k-WQ`xen6oWWAU5CBmo z;*z_bL#AT<Nq%dCm7u5SW`>M?f}-&Op(8SfN9Qz1xLg1Dn8ApZC+-zw1x2AL%F98M zQvk~ypbS42>zEZh76565YAeXZf?Ac8bE3p05Hw|lK~xsx%XJz^Kml;K{@G~llEc8@ zlSfnq%#@IMHKPyDd*PuA$|@o>#^{bmirXvtE+#Lfcb#bZq7c8L9ok3UAKrsRw@+7k z6#Id`$Y_-tm(tH@GJ3h#8)JJA2zMo5KX_Te$a*ajU0VE8qx0((TbG4gFeSAwRN^=} zt)sJ&R@<V{eOi#(()h-ldM?)U^HOBXS||Y{M>ANO)d;c0)Y8Fke^wdkLHU60>-&PF z|JQt_UwPSJtCMs+yU#at#Mx@A#Md1kCuyU<5-a`ax4XO;5sg-%{{Ju_Y~ZA&K3|Th z7s&Mi3bQA+m=$6+Tee4kHnhKm5ly|oVzxr}o!cZtJgPQK;(DXb35ylg^bwboH6x+H zhy!`(Mllz>`u*BWg03QL!fDwP8G_!iyH`Zz6K$AvdzQL;Z|={lu}E!fK#1$_ok>yZ z<?s=V;kUokc1(rtOWH)tcW#2O@8X!QwMXYOOOm5&*TrE>W$G23bWYhc6M_#uloc<Q zC2Yrc-<H{O^3(FBXELCnuB9W~@I5D6?!@?6oFbPZ)npycIdXUER-Q6W9$c^Qq^$nM zsQQZS-1UsL#+mkgyv4}q!nK%uefZv9)7g4%T6-iyv;BBJQ%#%_r))BHdwwv#Y;NNr z5b(M=uqz<yK0p$QS%ViMBp;dSQ3EvEX}NOat-%q49e&9I@x96x?f#@*#PnPnc!5;o z{qniKPxJWm{WP&SAHHOf9cfJ|;jbN@XZWXbtYO<msN5U$3rZfo7dzWL(;}bxWGNkR zDGi9KQ4#Ogjt4SiSc=12L<IUnH1P>~(9$35u0=r<5H|ECgP*|D_5OTaKFX;K;%j*M zXD3WL@|%4`6LrK3>#*uM1s+h`K07%Lw#1b+O)uISiAp>HneM7E0jI9}OZzte0Cnb* zSUSmb1;6(D29d$Jy}InBi@b)j$}-^YmB0#IOh5u18(F&TWJXBcmb1gyy7P=T2lut0 zpAFt3TGX8_dcuu}&3e~7L+x70?$BLm!AsSz96Z4Q>Ju`f|A`64;UwIWAo$1(_hrMc z-rn}71sNX(Nm*9BC|z3u-j+m)8_qCt5aXwd_0LzKt1HVPUgL;HQj9#}VBQh6VwdX( ztD|E+gF+v@3MQ4zeWl@*xhhoY!;<jp!+vU6g5yg?{FSumEXzEMr$EZrj%;t^a79KB zj;`{gFo+PDG)CVI>-h1oT>Eu#Bi6rMJzf4(PY?F>8Xt#|I^f+(#(mGi5j)yPVP;~w zT|;BFX9l;JM85&)Jt9nNLA~lno&I*ybbGup;lW{i<(&0QwpUjlk#sRu{}3^<C=^ev zvi;!F38bcA<r&eaHSx<ORKdAU!XVo95*4*~+Wi`ovM=<81nsO$o!*Y09&7IG=F1f? z3#eZ#o(D5X6=&Nq_oc(bpOuV-KdQqmyaRAgOq6?66&rYn6D|djhRk_g`drwcf|ZHI zgU68MeR-XT{=w-d-i19usGy8#Y7bdb*Xbbw|6|{YoAvv<j#OLb>wZ6?Fp~kK>-l-v z0CYhEEKT(heQEXics)w2$L0n~h_9=pKzoEy=*<@)%;|F;^cAHY2k$}k{+1JF+7D#s z$&EE6GtE16Fnl~b=TXPe@1j*}XeVP@-YapHHG0eHOf58AoBKp$+0cO7Dk2h+xbOAC z@)XOD?oO($pTt!`7K$&Rt^64R^Ho#{U!wK+x2x3F^b?Rig*XRnULF)LC|iDwq@kk3 zlVoKWD#W8V=CE5+QigD{Uy-t=24B|MLwr5YkKnfvbPglXBOK2zd0f8$LkTRl^Qs@; zz8Lql)aNz_ogWoH(>PT0xYcL{(AZ@|_?@cwyGRGaG!+9DrijyQy{I)nL)FnHdVOBl zjTm*a7Vn_kxlH?>gbEY^1GF}e4@bSd$}jh5;r4FhmpBwxs`+CiXE)Xb!ncsgF;9fw zn1`<l`!Y6zA?gRl$+S=Y=I6aP(9+1z1&}KnIp<E!11<q`Jax0k`S!8v(Xnfw?uhOy z?TJ1W3CcE=$k$f-w>(Jc)i%c5_R1z*5{ET$w1)==pJDNeDGw<q(2$N_JDcerTFSYb z=;`<?-w6w3B?x;)9^xKM^GUxqY{L&yb)JOhSOkwK^Hz|oAIgzIrAtWDhSsqd=2q%X zE2kOvqReXMX8be;qcV<7M?F+<Qz_JwrtktoPSj*{7GJarv5t=2;GHvm-SyB7U(GlE z$;^+7Ud(JWk%fuJNG8h4s!ikNsr%Xy&RY<FK3DK1GP!0ZvE10D6gz=Qqs++JRiK{< z>0OO=T^xm5NVv|u-6E$$4{yO+??-GY)MDI|YQgXtGm>%F{vx-jE~rvXCB4$|qpj3x z>Cb-yk$n7;wV4U+>Ow0zqki4Ky3NoU_813QQz>#~_%(^f+*u_w0hRzeG-Pt%^vo_1 z!C^_((`ly0X_y;3RH{B+*<IPoujeEXBqDwcj+j=SOiKP@%tD&6$??TOl=Fu*j-=;y zCNc`n1-sh>0L}=rnPBbc7bG!=$A9Mu-IzfQk0DKS8_U4`;_k7-DY{cRi1MC{x8{wV zn7p~HHKHWZCHGVvPIYE4!u&Cz$V=2F&@Gma#;VB>QH8A$TV*HsO0b@Y%9Q98qq~Dk zZ9uGk>lChx44MGx1$tImPs*3_>k4{NlKp0D9oZS4W?Zhlms2U-(~Pd*2VC~8GlXRS zS=jB6N)R``b0VXLl0w%8I||8fXRVS(-C<2Tty~DntDY9*21JSF1Mo42S8J2QB5+L* zyD!DOk-ZAirum98?)Ow7Pu0$tnyNX{42T9LSn5jmrKn|6V&YTo?L*|+e245D0Uidr zQfM{TqWA4beIAAvToq}Oa=5Hq^hy!3;5#<AJ?VC<%b#1Az54B)G9Ft!BiZD^0cBTl zJu3R8SOnsMP(Fk{RwuscQjm7F{%%Rs=~W8@a=M^~2|Y>2?ZVlUgdDFR%2g`c-riLZ z+ohT?j-9D27W)`0vt<D&SuPr>RfNmPR6ru|qKA@S;Kx&E5Hdx!AR}eQTQ+~&vN_~m zV8h&aVFGeTjndF+{%*~_7)8F-U#Jst$3v5?ol+jRi#v6FP(E?SXp)0M^wxhAZ%*1@ zr{Tov_m}-Z`V1$+QiK<wyy~K%-KNewpq5r2U`^!uRo~B9L}hGJRnm{@L4({!8Z`B3 zZVZiZPKIR7D0<B=qJv4^6var9^gwmI7o$>L*bfR`K{)Ojpem`L@|bwW-CZp)2*|XV zke#1^ASBu$C12D~cX}NQ<%xcA!=MpS1TS5KMz!gZpjtM@iOd6V12qHIjI<k}F-=`z z-za(vC{kuO8f1NSQNJ`MUbl$IC1+tLODPjVM)*(Stt@up*h@llQzZzqlAq&*)(iY! zMMOUezzJ&H8+fFz`a<`}`tbO@X75szvA*fXz9}N6IC4<r+kDNX?1c0h(|i#H#b5LV zEfzYnyr~Ij)BcKo(1cJBt;7!;0?Thg-dgmk4qxfAPZ%qY`3Rv!a@7+4K8~vD+|5A= zRqTt!jp;f`$OAF|#ySc`ntsd2={`<x*r=P4e%~+}17P!G{mMdqekm+nwGasK8Ba}N z6IFqT746jxiXDfsLsEVuw++qxjUi0)(#c4T!NbSJmyc~Cetn}xcTw%8TRL!OBbl;k zo;1n|v40&wk9;+dfEPEFM%s9&`Y)o6_hQ@aRQaQP5<<i5+xrDF3Y$#kK~Ov%hS2i9 z)Aucm(w&}H3&-QLN3+(dJM~LsjC=^M<z5vQ&$!uE`#p(<+CCOU&1uqK2>;8)QJoe- z5W>81Q8G!2hkH-Q-@409N)ac-qjMuI5ly@+NqEQDxnuiNe_(f$LU!`!1!3tK=}yq` z-qTc3ul$_~srYXOv@y3gt9?j@j=M?CJ4omvUN80J>z$tZ9#vO;6ftB-Ix4M3@p;BX z0^O#+LK#@OU;Px^#a05nY*&_)L%h3MUd+p{*fyz`C4H~ggsn$h+V^5230W*Gi$)&f zW0xsyA6{Lfl+2mo?vVPMw0RPuLdT-JaG--xZ_LPSt;@;nx+X7XdUw@^#WO{PeWq>; z<2rXwpVZQX^;hjhpJTY&S|Z#OcOff{d{Hvc*j?WYghTc2+O3W4M@wB}d;0_A!&@iE zoeR0Lub-v(3gox)@;!W}ZpKsGjV7{zCimqUU(N;{tw|fRGBe>l;w_oGvYZQ=S?{t= zxDD)sKyo~c)|_(1mo@ljG}DV4XLCgjr_oU#Dud@`)GcaN7o>aB!14o$a<A~tL4cCN z3_X}lAXzy&x>#QSZ7?TUjRM%^M(hQVKN8J3n8LmbvUIgL<%>jY&C=ZcIP@nS17lE@ z55<U*iV?+!PsDFdKxmDe0!D~AZQErr@+k}1i<|3oN~P30<J5h8%lA6e=twkqe%aXE z1dChh&NIlpEt+rtu-TTBYNo_|bG&u$unfr_Y@ioKfsJ!Nf!=(nGW!SKeRARi@mbLi zXwKOp*us>`X7*k@$itbQD0+v<Tps|A4nM84v8qkOoj>R3X_#i82&bB#hjK7ujB0IO zU(?sMQ>}>>PR2RBl}_dpxzW9<QE}>2tg)(52X}|ILu5bC1=iTUer5l$eS?{Z03T8a ztNTiLH?p6g>*4Pq9`@ETus-DHlUfY?{#+K7XHBGYEBd$ax+=u(9AWxRee~_GcSZfI z#!p(TZh^-eAIQ_b*mD(D#t#Xapxz8#Y|5Tx20iZ%=exZiIiJc^y;FFO2#;PGcXI6x z4{RDPw|*^Gfo*kjSZe#DwX-uMJbxP<MQeLaxrN*IjsT@G!nSrSeSI0MVdjj`?63}A zrcXj^OQ<2s3OrdvQNdgAa8I>;IFf|)F6CWtB#d!HFKe2P9ofir_`wKNlbbJZ^#pe> z{Le~H!Cf&W6(p+;uF0~qo{Vny8%dFyY(myHG!!r}*QJJ$=fMqwsZxe@IuAJL-9f&g z&G`VcKv`OJI*eH03)ytTj_XK$E5>%-L^nR#VS=Ik`#yB(1<$^EYI`vM5fj1`Oi~T$ z>Z?boBw!Y96KTQ56F=ve2rF;*Z;XSqtk_%P)f+4~iI{#G1rv??iYBYT;gu&St_jF8 zjQT#oQiLk6OL3_Q1dhwpB{xc{t%u#md`|NZ{9X}1d9I6oFS9CsC|{yGz|(~E;r#Wv zYq`3If0}6J$j;YRQ|RA@rJse5)akuq!Tj1x2P2k&Tj84ayBu6{?Iw<eAsSWtP&d*U z9Xs|R;#s3En`g@#=kzovJBu0FAqP0$v4jxy%9FG|0^a)5kX+sqpfI+xJ4mZF#lE9e zT{DtUS!5+bWYN!D(VhS*>>p*9_%?-dF%G($a_$2x!uq(Z@EpDu^5EzbRS_Rne5{0* z(cL`a59*ZI`}(%yIOKCpT^6yT_`L&THbQ;6D@f=)1PGwP=K~Pdhd+}@Q>=Vz6z7>{ zL?2i!g~eUER4CP5adLvgB_Iy@2SV_MVZ3}D4a2lg()uVJJ&<}5pX@|V+V`RKMyHy= zqS=q!Oe5hOS5Jq3Zht0nef*oszT;mzeY#^}qAm^ZUp_fW!ly~$X89pWBG&sCXTv%& zQy0OEfl~PHeLX2f)W49{sza;5!&cauz~B7h+A7NnhbsJs!Tofs*v#-*9@BEaH{X_T zzS9~>Xk!%d6TrBLk?d3C()tr<jzdRlLQD5A#qT=*y~Jh9UdcZ&y7gWE3xi#)sA{Gn zTpC_NGYT;A(miE=E?P{1bG6OVQWG~}EHkF3GV6c(M2?KS)eY#9)sR;3T1W`uA5`v7 z7x#KHI|al>c0}p7-nOzhsI6#I7QhNxHEmnWu-L1ymTynqO^m$D)z%W?O*MV|^RW~L z`wsga&&JAX<RX?B*+ty#6MA@YiRJ0Ad1{*0c{amqd8df^UWji@#oGw_d!=)Bv7H7_ zrx-K2J9#?3u}i<1wcS(ZE>zvgAe76m{W1Ngk?!rOLqLhYWJS*6`YyMoa?d;Z8vm2p z$^dw*sIF5eW3WjC#*(-a=1ua`l<DmsQ_Ba_lPnzobuQlMF@VZgd+nGZw}DUZZCY0{ z5vP#;xtX}f4II>xPM|8{llG!?U-8~JQ-=y;5gt5II^4zN>@1;Ue4UHwE~8}-Fhvx* zEFg$Fh>G-;0Wym4>0}Dqsu5W+*Y|U(c2C-yO$;0$(_5T2#UkJgk(`r{m$<8jwnpEN zPz`fu9?d-7_P`J$2WRa=0zU7k*xKgvkqr<-mlJ>gv<PE<h3cFh`K;8V<<Z@1be(~G z61=zCV^B|~0Cw3j6BQRR3Y|(U=)A`T+&i9bvFx9=nIf5Q@Ri)K2U@j_lW}Z{)YUqp z;^4MULy{DhqUqA*^g$E88M%7O<HgF69+=3tBRjA$REqz%e;+@=wPe1IQ0g~bRZ<37 z>O$pHkM9MZ=4zkT$vq3)`=*cbF4QdMV@s6-<qT!F!aa5M7ZV0*9bHEITe}_@lXQ>n z47Hl-ekrw=^YurqmY@c9yQN#-y-haBM+zBTy!2Ld%05~<iZTE6i_m9iwfQ#av|JnJ zma|xdOa)vQRpBhRH9vpmSkkxk)f%+%Q=w8LZJQ{hu!P($llgxAO5<O@1j5lycX{YA zU32+Ff<`lCcy-u4M=Gt+&ZKMZDvkY<pK)aNF?jGj%c%<KD}K|C=~pZUtTR_GI@!7w z`rV0d_H|89l7-CWCZ{gGZU^c>CFl)kZ-c#}&(kF6zD7g>)TrCbjFEw*dM;*jO_lq2 z3JG~Pc?8;sUlDD}4%&kb4)OjsOIWk)3(022Qayn!M2W8Pq==ICVu-3N>W^(_t`EC! z*ddMH7OLfwnVw}uj8Zvewk<oaan1gH{>PSYLMY>ym((NOIZm_~3(}D-i4qnj<0PCK zKhL@zC)iM*<duJt-$HBpm@=&WI{w?@X|HO)5}L}ZR>4R$B;wE1B@o3S^pUtmSk9s3 zZV<~kk)mWx*zY0PQ(`q0ojyIRIXS$gccp=2Q$N11$x4;`X@qz#%iJajZ4G)6;EL-Q zuc{j2P^6N$aum#!Qw^KV{QD;?rgGfJIYGIc--7?k^(x@s5wkdJ`FleY<Sd#o_z!5z zeck4LiKQ5RYeYfJ(>IdRx)S9aTofp>X5))-BoJ+|{mZgm=P*XeVQ=-aKn_L&qS&&5 z5$f==;)6Qj(OOOKZXPJh+OV<d>dl>VrRSzrBH7z9fGwk(!xA29t_X^?TjFv=PYtaO zp)o;aE0Eg4u=@Fmoe@)VMHhjxhm9hFPDR7<{<D@vnDh`*>F<%ZN2n>twB;;wNeOsq z1v%w`7BXNhc5I3QsN>vR+jkvI<R7ed4LZ2)sGL*yNRCpBfVf>@+sB=xgh6`H#GaFQ zoxHI}ur<gp?i=>DPW+I*60A{<HoTgF?gviYJQG|iOIU3E_f{Y<00(Ni(D7~J>!S=j ztt@6oC&zrl8|`UPZMjnh`u<(q3Q(DBlbrY==RihphPQh4f9(!v5`q4RR)LQzq$)T< zUcq>ifTWUlR3Q2s_|3`I(L8=um1&fU>+lT?s=@=QgMmrMW-rjc(9|q81|DPsU;1jD z+WEXVC3U2*?%e1q;>nd_-7WeZGsgapyU719t5o84Ejj_ryO60+qh$vF=J!c=A#RbI z>i{YS{@s1;zX0r1&1&`~X0rDhnNHwWJ-0ECJeLW$;)mjF5n*b4=0L{Zq(fU>uaRy( z^AT||+Ty`|_vbzs3E+u`Cp=xlc!f<cWyBM{q2*ObZk3Z_A#-;VUM<aCjf0Vq2uQc? zOSCZx`3P*LsOOaLGpp=AfAG~+*}0k^6t9&l3{5b)jv+q0+ur+2>uW`Mp-Y>(8HNlt zF}xQz(apDPV`r?Db$Llb0``B8w_k^r7i2CSsHHTuPC8Rj5ADqnleJ_UCxGR3te)Lg zUVIGv9NN>%7Z9*p&A?d_Je8L^M*f!)Lz-PBLW^*I#jkA3;Yk2lsa>){HM~c`jwBH+ z@6DdSzvf9jGR(vym;j3m=dwc+>0{pO&P&fa9b6JYs#-<bX@gn)zkY&@Y7Q!qn31?2 z%q=^gbN(oQ_CJ-ZeSWH<#qU$C#k^^3Z>YhyHwtny33H<(K>|G{{%L+0AtD{p8T%S( zp+HpDa&|k1^pyf3n(d53b0n3HAn1v`ol1@TNRUsnbL;X$xHUK)7RAZ0z)pr1FT|-C zz1~CqS$C<LGw)x4E~X+n)z;f(te1>O;6hO1ZD5T^@*jfQoal9hFJcdRQTzjeIwf|u wHwc|=J8BZ%9!Dukp@NEWW)ca|lky|BFE6XE++V5L=O3wns-l*{S1<(nfBRNq_y7O^ literal 0 HcmV?d00001 From eafa82bbd6aaa7f71af6341b626a1b1629334100 Mon Sep 17 00:00:00 2001 From: Major <major.timre@gmail.com> Date: Thu, 15 Jul 2021 20:50:02 +0200 Subject: [PATCH 69/69] GDPR Consent docs. --- .github/docs/GdprConsent.md | 240 +++++++++++++----- .../GdprBanner.razor | 4 +- .../GdprModal.razor | 4 +- .../IGdprConsentNotificationService.cs | 2 +- .../IGdprConsentService.cs | 2 +- ...ajorsoft.Blazor.Components.GdprConsent.xml | 12 +- 6 files changed, 189 insertions(+), 75 deletions(-) diff --git a/.github/docs/GdprConsent.md b/.github/docs/GdprConsent.md index a080ffa9..3e9ee57e 100644 --- a/.github/docs/GdprConsent.md +++ b/.github/docs/GdprConsent.md @@ -22,71 +22,43 @@ You can try it out by using the [demo app](https://blazorextensions.z6.web.core. - **`GdprBanner`**: renders a small Overlay layer at the bottom of the page with customizable content for showing the given GDPR message. - **`GdprModal`**: renders a Modal dialog with Overlay layer for the whole page with customizable content for showing the given GDPR message. - **`IGdprConsentService`**: injectable service to handle GDPR Consent actions. -- **`IGdprConsentNotificationService`**: injectable singleton service to handle GDPR Consent changes. +- **`IGdprConsentNotificationService`**: injectable singleton service to handle GDPR Consent changes with events. ## `GdprBanner` component -Modal dialogs are positioned over everything else in the document with optional Overlay (customizable opacity and color). -Modal dialogs are removed from DOM when closed. **Only one modal window can be shown at a time.** -Dialog automatically (can be disabled) focuses itself to capture keyboard events when closed will refocus the last element. +`GdprBanner` renders a small Overlay layer at the bottom of the page with customizable content for showing the given GDPR message. +**Only one consent component allowed per Application.** ### Properties -- **`Header`: `RenderFragment` HTML content** <br /> -HTML content to show on the Modal header (top), right to the close button (if visible). Can be any valid HTML but should be only Title text. -**Must not be defined if you want to leave it out. Also `ShowCloseButton` must be set to `false`** - **`Content`: `RenderFragment` HTML content - Required** <br /> Required HTML content to show on the Modal dialog. Can be any valid HTML. -- **`Footer`: `RenderFragment` HTML content** <br /> -HTML content to show on the Modal footer (bottom). Can be any valid HTML but should be only custom action buttons. -**Must not be defined if you want to leave it out.** -- **`OverlayBackgroundColor`: `string { get; set; }` (default: "gray")** <br /> +- **`BannerBackgroundColor`: `string { get; set; }` (default: "gray")** <br /> Sets the `style` of the HTML `<div>` `background-color`. Use HTML specified: **Color Names**, **RGB** or with **HEX** values. -- **`OverlayOpacity`: `double { get; set; }` (default: 0.9)** <br /> +- **`BannerOpacity`: `double { get; set; }` (default: 0.9)** <br /> Opacity of the overlay `<div>`. Value should be **between 0..1**. Where 0 means the overlay layer is not visible. -- **`CloseOnOverlayClick`: `bool { get; set; }` (default: true)** <br /> -When `true` Modal dialog will be closed when Overlay (background) clicked. It works even if Overlay not visible (Opacity is set to 0) -- **`CloseOnEscapeKey`: `bool { get; set; }` (default: true)** <br /> -When `true` Modal dialog will be closed when **Esc** (Escape) key pressed. -- **`Height`: `double { get; set; }` (default: 0)** <br /> -Modal dialog window Height in **px** if set to **0** Height is set **auto**. -- **`Width`: `double { get; set; }` (default: 0)** <br /> -Modal dialog window Width in **px** if set to **0** Width is set **auto**. -- **`MinHeight`: `double { get; set; }` (default: 200)** <br /> -Modal dialog window minimum Height in **px**. -- **`MinWidth`: `double { get; set; }` (default: 200)** <br /> -Modal dialog window minimum Width in **px**. -- **`Focus`: `bool { get; set; }` (default: true)** <br /> -When `true` Modal dialog will automatically set focus to itself when it opens, and set it bact to the last focused element when it closes. -In general this should never be set to false as it makes the Modal less accessible to screen-readers, etc. -- **`Animate`: `bool { get; set; }` (default: true)** <br /> -When `true` Modal dialog will appear and disappear by using smooth CSS slide and fade transitions. -- **`Centered`: `bool { get; set; }` (default: false)** <br /> -When `true` Modal dialog will be vertically centered, otherwise shown near to the top. Modal dialog horizontally always centered. -- **`ShowCloseButton`: `bool { get; set; }` (default: true)** <br /> - When `true` Modal dialog will show Header (even if Header is not defined) with closed **x** button. -- **`IsOpen`: `bool { get; }`** <br /> - Returns `true` if the Modal dialog is opened, otherwise `false`. +- **`ConsentDetails`: `IEnumerable<GdprConsentDetail> { get; set; }` (default: new GdprConsentDetail[] { new GdprConsentDetail() { ConsentName = "Cookies.All" } })** <br /> +GDPR Cookies consent details. An enumerable list of Cookie types with Accepted flag. +- **`AnswerValidUntil`: `DateTime { get; set; }` (default: DateTime.Now.AddMonths(1))** <br /> +Gets or Sets Consent choice validity date. After this date Consent will be asked again. +- **`InnerElementReference`: `ElementReference { get; }`** +Exposes a Blazor `ElementReference` of the wrapped around HTML element. It can be used e.g. for JS interop, etc. **Arbitrary HTML attributes e.g.: `id="diag1"` will be passed to the corresponding rendered root HTML element Overlay `<div>`**. ### Functions -- **`Open()`: `Task Open()`** <br /> -When method called Modal dialog will be opened. It should be `await`-ed. -- **`Close()`: `Task Close()`** <br /> -When method called Modal dialog will be closed. It should be `await`-ed. +- **`AcceptAll()`: `ValueTask AcceptAll()`** <br /> +Accepting all GDPR Consents. It should be `await`-ed. +- **`RejectAll()`: `ValueTask RejectAll()`** <br /> +Rejecting all GDPR Consents. It should be `await`-ed. - **`DisposeAsync()`: `Task DisposeAsync()`** <br /> Component implements `IAsyncDisposable` interface Blazor framework will call it when parent removed from render tree. ## `GdprModal` component -Modal dialogs are positioned over everything else in the document with optional Overlay (customizable opacity and color). -Modal dialogs are removed from DOM when closed. **Only one modal window can be shown at a time.** -Dialog automatically (can be disabled) focuses itself to capture keyboard events when closed will refocus the last element. +`GdprModal` renders a Modal dialog with Overlay layer for the whole page with customizable content for showing the given GDPR message. +**Only one consent component allowed per Application.** ### Properties -- **`Header`: `RenderFragment` HTML content** <br /> -HTML content to show on the Modal header (top), right to the close button (if visible). Can be any valid HTML but should be only Title text. -**Must not be defined if you want to leave it out. Also `ShowCloseButton` must be set to `false`** - **`Content`: `RenderFragment` HTML content - Required** <br /> Required HTML content to show on the Modal dialog. Can be any valid HTML. - **`Footer`: `RenderFragment` HTML content** <br /> @@ -96,10 +68,6 @@ HTML content to show on the Modal footer (bottom). Can be any valid HTML but sho Sets the `style` of the HTML `<div>` `background-color`. Use HTML specified: **Color Names**, **RGB** or with **HEX** values. - **`OverlayOpacity`: `double { get; set; }` (default: 0.9)** <br /> Opacity of the overlay `<div>`. Value should be **between 0..1**. Where 0 means the overlay layer is not visible. -- **`CloseOnOverlayClick`: `bool { get; set; }` (default: true)** <br /> -When `true` Modal dialog will be closed when Overlay (background) clicked. It works even if Overlay not visible (Opacity is set to 0) -- **`CloseOnEscapeKey`: `bool { get; set; }` (default: true)** <br /> -When `true` Modal dialog will be closed when **Esc** (Escape) key pressed. - **`Height`: `double { get; set; }` (default: 0)** <br /> Modal dialog window Height in **px** if set to **0** Height is set **auto**. - **`Width`: `double { get; set; }` (default: 0)** <br /> @@ -108,28 +76,47 @@ Modal dialog window Width in **px** if set to **0** Width is set **auto**. Modal dialog window minimum Height in **px**. - **`MinWidth`: `double { get; set; }` (default: 200)** <br /> Modal dialog window minimum Width in **px**. -- **`Focus`: `bool { get; set; }` (default: true)** <br /> -When `true` Modal dialog will automatically set focus to itself when it opens, and set it bact to the last focused element when it closes. -In general this should never be set to false as it makes the Modal less accessible to screen-readers, etc. -- **`Animate`: `bool { get; set; }` (default: true)** <br /> When `true` Modal dialog will appear and disappear by using smooth CSS slide and fade transitions. - **`Centered`: `bool { get; set; }` (default: false)** <br /> When `true` Modal dialog will be vertically centered, otherwise shown near to the top. Modal dialog horizontally always centered. -- **`ShowCloseButton`: `bool { get; set; }` (default: true)** <br /> - When `true` Modal dialog will show Header (even if Header is not defined) with closed **x** button. -- **`IsOpen`: `bool { get; }`** <br /> - Returns `true` if the Modal dialog is opened, otherwise `false`. +- **`ConsentDetails`: `IEnumerable<GdprConsentDetail> { get; set; }` (default: new GdprConsentDetail[] { new GdprConsentDetail() { ConsentName = "Cookies.All" } })** <br /> +GDPR Cookies consent details. An enumerable list of Cookie types with Accepted flag. +- **`AnswerValidUntil`: `DateTime { get; set; }` (default: DateTime.Now.AddMonths(1))** <br /> +Gets or Sets Consent choice validity date. After this date Consent will be asked again. **Arbitrary HTML attributes e.g.: `id="diag1"` will be passed to the corresponding rendered root HTML element Overlay `<div>`**. ### Functions -- **`Open()`: `Task Open()`** <br /> -When method called Modal dialog will be opened. It should be `await`-ed. -- **`Close()`: `Task Close()`** <br /> -When method called Modal dialog will be closed. It should be `await`-ed. +- **`SaveChoice()`: `ValueTask SaveChoice()`** <br /> +Saves user chosen GDPR Consent details. It should be `await`-ed. - **`DisposeAsync()`: `Task DisposeAsync()`** <br /> Component implements `IAsyncDisposable` interface Blazor framework will call it when parent removed from render tree. +## `IGdprConsentService` +Injectable service to handle GDPR Consent actions. + +### Properties +- **`ConsentStoreKeyName`: `string { get; }`** <br /> +Gets GDPR Consent Browser local storage key name. +- **`ConsentNotificationService`: `IGdprConsentNotificationService { get; }`** <br /> +Gets a `IGdprConsentNotificationService` to able to subscribe on Consent changed events. + +### Functions +- **`GetGdprConsentDataAsync()`: `ValueTask<GdprConsentData> GetGdprConsentDataAsync()`** <br /> +Gets the GDPR Consent data from Browser local storage if any stored. +- **`SetGdprConsentDataAsync()`: `ValueTask SetGdprConsentDataAsync(GdprConsentData gdprConsentData)`** <br /> +Sets or overrides the given GDPR Consent data in Browser local storage. +- **`ClearGdprConsentDataAsync()`: `ValueTask ClearGdprConsentDataAsync()`** <br /> +Removes the GDPR Consent data from Browser local storage if any stored. + +## `IGdprConsentNotificationService` +Injectable singleton service to handle GDPR Consent changes with events. + +### Functions +- **`GdprConsentStateChanged()`: `event ConsentNotificationEventHandler GdprConsentStateChanged`** <br /> +Event handler to subscribe for GDPR Consent changes. +- **`OnChange()`: `void OnChange()`** <br /> +Method called by `IGdprConsentService` to trigger Change event when GDPR Consent value was changed. # Configuration @@ -144,6 +131,9 @@ Use the `--version` option to specify a [preview version](https://www.nuget.org/ ## Usage +**Use MainLayout.razor file for Blazor Apps to activate GDPR Consents only once! +Only one of the GDPR component types should be used (active) at once not both. Only one consent component allowed per Application.** + Add using statement to your Blazor `<component/page>.razor` file. Or globally reference it into `_Imports.razor` file. ``` @@ -195,12 +185,136 @@ public void ConfigureServices(IServiceCollection services) ### `GdprBanner` usage -``` +Renders a small Overlay layer at the bottom of the page with customizable content for showing the given GDPR message. +**Only one of the GDPR component types should be used (active) at once not both. Apply it on a common place in your App e.g.: MainLayout.razor!** +``` +@*THIS should be in a common part of your App e.g.: MainLayout.razor!!!*@ +<style> + .gdpr-banner { + font-size: medium; + font-weight: 500; + padding: 16px 0; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + /*border: 1px solid #e0e0e0;*/ + } +</style> +<GdprBanner @ref="_gdprBanner" + BannerOpacity="@(_bannerOpacity / 100)" + BannerBackgroundColor="@_bannerColor" + AnswerValidUntil="@DateTime.Now.AddDays(_bannerConsentValidDays)" + ConsentDetails="@_gdprConsents"> + <Content> + <div class="gdpr-banner"> + <span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> + <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent Banner usage in your Blazor Application.</strong> + + <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprBanner.AcceptAll()">I agree</button> + <button type="button" class="btn btn-secondary m-1" @onclick="async () => await _gdprBanner.RejectAll()">Disagree</button> + + <button type="button" class="btn btn-warning m-1 ml-4" @onclick='() => { _gdprControlType = "Modal"; }'>Customize</button> + </div> + </Content> +</GdprBanner> + +@code { + private List<GdprConsentDetail> _gdprConsents; + protected override void OnInitialized() + { + _gdprConsents = new List<GdprConsentDetail>() + { + new GdprConsentDetail() { ConsentName = "Required", IsAccepted = true }, + new GdprConsentDetail() { ConsentName = "Session", IsAccepted = true }, + new GdprConsentDetail() { ConsentName = "Tracking", IsAccepted = true }, + }; + } + + //GDPR banner + private GdprBanner _gdprBanner; + private string _bannerColor = "lightblue"; + private int _bannerConsentValidDays = 20; + private double _bannerOpacity = 90; +} ``` ### `GdprModal` usage +Renders a Modal dialog with Overlay layer for the whole page with customizable content for showing the given GDPR message. +**Only one of the GDPR component types should be used (active) at once not both. Apply it on a common place in your App e.g.: MainLayout.razor!** ``` - +@*THIS should be in a common part of your App e.g.: MainLayout.razor!!!*@ +<GdprModal @ref="_gdprModal" + OverlayBackgroundColor="@_overlayColor" + OverlayOpacity="@(_overlayOpacity /100)" + ConsentDetails="@_gdprConsents" + Centered="true"> + <Content> + <div class="container-fluid p-3 mb-3"> + <div class="row"> + <div class="col-12"> + <h2 class="" style="justify-content: center;"><span class="fa fa-lg fa-cookie-bite m-1" aria-hidden="true"></span> Cookie Consent</h2> + </div> + </div> + <div class="row mt-3 mb-4"> + <div class="col-12"> + <strong>This demo site actually does NOT uses cookies. Only demonstrate Cookie Consent Modal usage in your Blazor Application.</strong> + </div> + </div> + <div class="row mb-2"> + <div class="col-10"> + Required Cookies: + </div> + <div class="col-2"> + <ToggleSwitch Checked="_gdprConsents[0].IsAccepted" Disabled="true" Width="60" Height="25" /> + </div> + <hr /> + </div> + <div class="row mb-1"> + <div class="col-10"> + Session all Cookies: + </div> + <div class="col-2"> + <ToggleSwitch @bind-Checked="_gdprConsents[1].IsAccepted" @bind-Checked:event="OnToggleChanged" Width="60" Height="25" /> + </div> + </div> + <div class="row mb-1"> + <div class="col-10"> + Tracking all Cookies: + </div> + <div class="col-2"> + <ToggleSwitch @bind-Checked="_gdprConsents[2].IsAccepted" @bind-Checked:event="OnToggleChanged" Width="60" Height="25" /> + </div> + </div> + </div> + </Content> + <Footer> + <button type="button" class="btn btn-primary m-1" @onclick="async () => await _gdprModal.SaveChoice()">Confirm my choice</button> + <button type="button" class="btn btn-secondary m-1" + @onclick="async () => { _gdprConsents.ForEach(f => f.IsAccepted = true); await _gdprModal.SaveChoice(); }">Accept all</button> + </Footer> +</GdprModal> + +@code { + private List<GdprConsentDetail> _gdprConsents; + protected override void OnInitialized() + { + _gdprConsents = new List<GdprConsentDetail>() + { + new GdprConsentDetail() { ConsentName = "Required", IsAccepted = true }, + new GdprConsentDetail() { ConsentName = "Session", IsAccepted = true }, + new GdprConsentDetail() { ConsentName = "Tracking", IsAccepted = true }, + }; + } + + //GDPR popup + private GdprModal _gdprModal; + private string _overlayColor = "lightgray"; + private double _overlayOpacity = 70; +} ``` \ No newline at end of file diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor index f09a7901..3f39f152 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprBanner.razor @@ -80,7 +80,7 @@ public Dictionary<string, object> AllOtherAttributes { get; set; } /// <summary> - /// Accepting all GDPR Consents. + /// Accepting all GDPR Consents. It should be `await`-ed. /// </summary> /// <returns>ValueTask</returns> public async ValueTask AcceptAll() @@ -90,7 +90,7 @@ } /// <summary> - /// Rejecting all GDPR Consents. + /// Rejecting all GDPR Consents. It should be `await`-ed. /// </summary> /// <returns>ValueTask</returns> public async ValueTask RejectAll() diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor b/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor index 38d7daa4..5f4358f9 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor +++ b/src/Majorsoft.Blazor.Components.GdprConsent/GdprModal.razor @@ -67,7 +67,7 @@ [Parameter] public double OverlayOpacity { get; set; } = 0.7; /// <summary> - /// GDPR Cookies consent details. An enumerable list of Cookie types with Accpeted flag. + /// GDPR Cookies consent details. An enumerable list of Cookie types with Accepted flag. /// </summary> [Parameter] public IEnumerable<GdprConsentDetail> ConsentDetails { get; set; } = new GdprConsentDetail[] { new GdprConsentDetail() { ConsentName = "Cookies.All" } }; @@ -107,7 +107,7 @@ /// <summary> - /// Save user choosen GDPR Consents. + /// Saves user chosen GDPR Consent details. It should be `await`-ed. /// </summary> /// <returns>ValueTask</returns> public async ValueTask SaveChoice() diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentNotificationService.cs b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentNotificationService.cs index b4944d7d..9ffcad74 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentNotificationService.cs +++ b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentNotificationService.cs @@ -1,7 +1,7 @@ namespace Majorsoft.Blazor.Components.GdprConsent { /// <summary> - /// Injectable singleton service to handle GDPR Consent changes. + /// Injectable singleton service to handle GDPR Consent changes with events. /// </summary> public interface IGdprConsentNotificationService { diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs index 2d17583b..ff67b6fb 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs +++ b/src/Majorsoft.Blazor.Components.GdprConsent/IGdprConsentService.cs @@ -8,7 +8,7 @@ namespace Majorsoft.Blazor.Components.GdprConsent public interface IGdprConsentService { /// <summary> - /// Gets GDPR Consent Browser local storage key name + /// Gets GDPR Consent Browser local storage key name. /// </summary> string ConsentStoreKeyName { get; } diff --git a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml index ef132897..9ddc4f66 100644 --- a/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml +++ b/src/Majorsoft.Blazor.Components.GdprConsent/Majorsoft.Blazor.Components.GdprConsent.xml @@ -83,7 +83,7 @@ </member> <member name="T:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentNotificationService"> <summary> - Injectable singleton service to handle GDPR Consent changes. + Injectable singleton service to handle GDPR Consent changes with events. </summary> </member> <member name="E:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentNotificationService.GdprConsentStateChanged"> @@ -103,7 +103,7 @@ </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.ConsentStoreKeyName"> <summary> - Gets GDPR Consent Browser local storage key name + Gets GDPR Consent Browser local storage key name. </summary> </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.IGdprConsentService.ConsentNotificationService"> @@ -153,7 +153,7 @@ </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.ConsentDetails"> <summary> - GDPR Cookies consent details. An enumerable list of Cookie types with Accpeted flag. + GDPR Cookies consent details. An enumerable list of Cookie types with Accepted flag. </summary> </member> <member name="P:Majorsoft.Blazor.Components.GdprConsent.GdprModal.AnswerValidUntil"> @@ -193,7 +193,7 @@ </member> <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprModal.SaveChoice"> <summary> - Save user choosen GDPR Consents. + Saves user chosen GDPR Consent details. It should be `await`-ed. </summary> <returns>ValueTask</returns> </member> @@ -239,13 +239,13 @@ </member> <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.AcceptAll"> <summary> - Accepting all GDPR Consents. + Accepting all GDPR Consents. It should be `await`-ed. </summary> <returns>ValueTask</returns> </member> <member name="M:Majorsoft.Blazor.Components.GdprConsent.GdprBanner.RejectAll"> <summary> - Rejecting all GDPR Consents. + Rejecting all GDPR Consents. It should be `await`-ed. </summary> <returns>ValueTask</returns> </member>