| <Router AppAssembly="@typeof(Program).Assembly"> | |||||
| <Found Context="routeData"> | |||||
| <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> | |||||
| </Found> | |||||
| <NotFound> | |||||
| <LayoutView Layout="@typeof(MainLayout)"> | |||||
| <p>Sorry, there's nothing at this address.</p> | |||||
| </LayoutView> | |||||
| </NotFound> | |||||
| </Router> | |||||
| <UserDataComponent> | |||||
| <Router AppAssembly="@typeof(Program).Assembly"> | |||||
| <Found Context="routeData"> | |||||
| <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> | |||||
| </Found> | |||||
| <NotFound> | |||||
| <LayoutView Layout="@typeof(MainLayout)"> | |||||
| <p>Sorry, there's nothing at this address.</p> | |||||
| </LayoutView> | |||||
| </NotFound> | |||||
| </Router> | |||||
| </UserDataComponent> |
| @page "/account" | @page "/account" | ||||
| @inject NavigationManager NavigationManager | @inject NavigationManager NavigationManager | ||||
| @inject UserDataProvider UserDataProvider | |||||
| <h1>Counter</h1> | |||||
| <div class="row px-3 h-100"> | |||||
| <div class="row no-gutters align-items-start w-100"> | |||||
| <div class="row no-gutters w-100" style="padding-top:2em"> | |||||
| <div class="col-6" style="padding-right:0.5em"> | |||||
| <MatStringField Class="w-100" Label="Firstname" Outlined="true" type="text" @bind-Value="@State.Firstname"></MatStringField> | |||||
| </div> | |||||
| <div class="col-6" style="padding-left:0.5em"> | |||||
| <MatStringField Class="w-100" Label="Lastname" Outlined="true" type="text" @bind-Value="@State.Lastname"></MatStringField> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row no-gutters align-items-center w-100"> | |||||
| <div class="col-12"> | |||||
| <MatStringField Class="w-100" Label="Address" Outlined="true"></MatStringField> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row no-gutters align-items-center w-100"> | |||||
| <div class="col-4" style="padding-right:0.5em"> | |||||
| <MatStringField Class="w-100" Label="Zip" Outlined="true"></MatStringField> | |||||
| </div> | |||||
| <div class="col-8" style="padding-left:0.5em"> | |||||
| <MatStringField Class="w-100" Label="City" Outlined="true"></MatStringField> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row no-gutters align-items-center w-100"> | |||||
| <div class="col-12"> | |||||
| <MatStringField Class="w-100" Label="Phone" Outlined="true"></MatStringField> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row no-gutters align-items-center w-100"> | |||||
| <div class="col-12"> | |||||
| <MatStringField Class="w-100" Label="E-Mail" Outlined="true"></MatStringField> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row no-gutters align-items-end justify-content-center w-100" style="padding-bottom:2em"> | |||||
| <MatButton Class="w-100" Raised="true" @onclick="SaveUserData">Speichern</MatButton> | |||||
| </div> | |||||
| </div> | |||||
| <p>Current count: @currentCount</p> | |||||
| <MatList> | |||||
| <MatListItem> | |||||
| <MatButton Raised="true" @onclick="IncrementCount">Click me!</MatButton> | |||||
| </MatListItem> | |||||
| </MatList> | |||||
| @code { | @code { | ||||
| private int currentCount = 0; | |||||
| private void IncrementCount() { | |||||
| currentCount++; | |||||
| async private void SaveUserData() { | |||||
| await UserDataProvider.Save(); | |||||
| } | } | ||||
| [CascadingParameter] | |||||
| public UserData State { get; set; } | |||||
| } | } | ||||
| builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); | ||||
| builder.Services.AddMatBlazor(); | builder.Services.AddMatBlazor(); | ||||
| builder.Services.AddSingleton<AppState>(); | builder.Services.AddSingleton<AppState>(); | ||||
| builder.Services.AddScoped<UserDataProvider>(); | |||||
| await builder.Build().RunAsync(); | await builder.Build().RunAsync(); | ||||
| } | } |
| using System.ComponentModel; | |||||
| using System.Runtime.CompilerServices; | |||||
| namespace CaritasPWA.Shared { | |||||
| // The class that stores the user settings | |||||
| public class UserData : INotifyPropertyChanged { | |||||
| private string firstname; | |||||
| private string lastname; | |||||
| public string Firstname { | |||||
| get => firstname; | |||||
| set { | |||||
| firstname = value; | |||||
| RaisePropertyChanged(); | |||||
| } | |||||
| } | |||||
| public string Lastname { | |||||
| get => lastname; | |||||
| set { | |||||
| lastname = value; | |||||
| RaisePropertyChanged(); | |||||
| } | |||||
| } | |||||
| public event PropertyChangedEventHandler PropertyChanged; | |||||
| private void RaisePropertyChanged([CallerMemberName] string propertyName = null) { | |||||
| PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); | |||||
| } | |||||
| } | |||||
| } |
| @inject UserDataProvider UserDataProvider | |||||
| @implements IDisposable | |||||
| @if (state == null) { | |||||
| <p>loading...</p> | |||||
| } else { | |||||
| <CascadingValue Value="@state" IsFixed="false">@ChildContent</CascadingValue> | |||||
| } | |||||
| @code{ | |||||
| private UserData state = null; | |||||
| [Parameter] | |||||
| public RenderFragment ChildContent { get; set; } | |||||
| protected override async Task OnInitializedAsync() { | |||||
| UserDataProvider.Changed += UserDataChanged; | |||||
| await Refresh(); | |||||
| } | |||||
| public void Dispose() { | |||||
| UserDataProvider.Changed -= UserDataChanged; | |||||
| } | |||||
| private async void UserDataChanged(object sender, EventArgs e) { | |||||
| await InvokeAsync(async () => { | |||||
| await Refresh(); | |||||
| StateHasChanged(); | |||||
| }); | |||||
| } | |||||
| private async Task Refresh() { | |||||
| state = await UserDataProvider.Get(); | |||||
| } | |||||
| } |
| using Microsoft.JSInterop; | |||||
| using System; | |||||
| using System.ComponentModel; | |||||
| using System.Threading.Tasks; | |||||
| namespace CaritasPWA.Shared { | |||||
| public sealed class UserDataProvider { | |||||
| private const string KeyName = "state"; | |||||
| private readonly IJSRuntime _jsRuntime; | |||||
| private bool _initialized; | |||||
| private UserData _data; | |||||
| public event EventHandler Changed; | |||||
| public bool AutoSave { get; set; } = true; | |||||
| public UserDataProvider(IJSRuntime jsRuntime) { | |||||
| _jsRuntime = jsRuntime; | |||||
| } | |||||
| public async ValueTask<UserData> Get() { | |||||
| if (_data != null) | |||||
| return _data; | |||||
| // Register the Storage event handler. This handler calls OnStorageUpdated when the storage changed. | |||||
| // This way, you can reload the settings when another instance of the application (tab / window) save the settings | |||||
| if (!_initialized) { | |||||
| // Create a reference to the current object, so the JS function can call the public method "OnStorageUpdated" | |||||
| var reference = DotNetObjectReference.Create(this); | |||||
| await _jsRuntime.InvokeVoidAsync("BlazorRegisterStorageEvent", reference); | |||||
| _initialized = true; | |||||
| } | |||||
| // Read the JSON string that contains the data from the local storage | |||||
| UserData result; | |||||
| var str = await _jsRuntime.InvokeAsync<string>("BlazorGetLocalStorage", KeyName); | |||||
| if (str != null) { | |||||
| result = System.Text.Json.JsonSerializer.Deserialize<UserData>(str) ?? new UserData(); | |||||
| } else { | |||||
| result = new UserData(); | |||||
| } | |||||
| // Register the OnPropertyChanged event, so it automatically persists the settings as soon as a value is changed | |||||
| result.PropertyChanged += OnPropertyChanged; | |||||
| _data = result; | |||||
| return result; | |||||
| } | |||||
| public async Task Save() { | |||||
| var json = System.Text.Json.JsonSerializer.Serialize(_data); | |||||
| await _jsRuntime.InvokeVoidAsync("BlazorSetLocalStorage", KeyName, json); | |||||
| } | |||||
| // Automatically persist the settings when a property changed | |||||
| private async void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { | |||||
| if (AutoSave) { | |||||
| await Save(); | |||||
| } | |||||
| } | |||||
| // This method is called from BlazorRegisterStorageEvent when the storage changed | |||||
| [JSInvokable] | |||||
| public void OnStorageUpdated(string key) { | |||||
| if (key == KeyName) { | |||||
| // Reset the settings. The next call to Get will reload the data | |||||
| _data = null; | |||||
| Changed?.Invoke(this, EventArgs.Empty); | |||||
| } | |||||
| } | |||||
| } | |||||
| } |
| </head> | </head> | ||||
| <body> | <body> | ||||
| <app>Loading...</app> | <app>Loading...</app> | ||||
| <div id="blazor-error-ui"> | <div id="blazor-error-ui"> | ||||
| </div> | </div> | ||||
| <script src="_framework/blazor.webassembly.js"></script> | <script src="_framework/blazor.webassembly.js"></script> | ||||
| <script>navigator.serviceWorker.register('service-worker.js');</script> | <script>navigator.serviceWorker.register('service-worker.js');</script> | ||||
| <script> | |||||
| function BlazorSetLocalStorage(key, value) { | |||||
| localStorage.setItem(key, value); | |||||
| } | |||||
| function BlazorGetLocalStorage(key) { | |||||
| return localStorage.getItem(key); | |||||
| } | |||||
| function BlazorRegisterStorageEvent(component) { | |||||
| window.addEventListener("storage", async e => { | |||||
| await component.invokeMethodAsync("OnStorageUpdated", e.key); | |||||
| }); | |||||
| } | |||||
| </script> | |||||
| </body> | </body> | ||||
| </html> | </html> | ||||