Skip to content

Commit babddbf

Browse files
See CHANGELOG for details - added Blazor interop for install notification
1 parent 5632f4d commit babddbf

8 files changed

+139
-19
lines changed

Blazor.PWA.MSBuild.Tasks/Blazor.PWA.MSBuild.Tasks.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ I will add more network caching strategies, but for now it has just one - cache
6969
</ItemGroup>
7070

7171
<ItemGroup>
72+
<None Remove="Templates\ServiceWorker\sw_register-beforeinstallprompt.template.js" />
7273
<None Remove="Templates\ServiceWorker\sw_register-installable-banner.template.js" />
74+
<None Remove="Templates\ServiceWorker\sw_register-installable-blazor.template.js" />
7375
<None Remove="Templates\ServiceWorker\sw_register-update-alert.template.js" />
7476
</ItemGroup>
7577

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
window.addEventListener('beforeinstallprompt', function (e) {
2+
// Prevent Chrome 67 and earlier from automatically showing the prompt
3+
e.preventDefault();
4+
// Stash the event so it can be triggered later.
5+
window.PWADeferredPrompt = e;
6+
7+
showAddToHomeScreen();
8+
9+
});

Blazor.PWA.MSBuild.Tasks/Templates/ServiceWorker/sw_register-installable-banner.template.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ function showAddToHomeScreen() {
66

77
pwaInstallPrompt.id = 'pwa-install-prompt';
88
pwaInstallPrompt.style.position = 'absolute';
9-
pwaInstallPrompt.style.bottom = '1rem';
10-
pwaInstallPrompt.style.left = '1rem';
11-
pwaInstallPrompt.style.right = '1rem';
12-
pwaInstallPrompt.style.padding = '0.3rem';
9+
pwaInstallPrompt.style.bottom = '0.1rem';
10+
pwaInstallPrompt.style.left = '0.1rem';
11+
pwaInstallPrompt.style.right = '0.1rem';
12+
pwaInstallPrompt.style.padding = '0.5rem';
1313
pwaInstallPrompt.style.display = 'flex';
1414
pwaInstallPrompt.style.backgroundColor = 'lightslategray';
1515
pwaInstallPrompt.style.color = 'white';
1616
pwaInstallPrompt.style.fontFamily = 'sans-serif';
17-
pwaInstallPrompt.style.fontSize = '1.2rem';
17+
pwaInstallPrompt.style.fontSize = '1.3rem';
1818
pwaInstallPrompt.style.borderRadius = '4px';
1919

2020
pwaInstallButton.style.marginLeft = 'auto';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+

2+
function showAddToHomeScreen() {
3+
DotNet.invokeMethodAsync(blazorAssembly, blazorInstallMethod)
4+
.then(function () { }, function (er) { setTimeout(showAddToHomeScreen, 1000); });
5+
}
6+
7+
window.BlazorPWA = {
8+
installPWA: function () {
9+
if (window.PWADeferredPrompt) {
10+
window.PWADeferredPrompt.prompt();
11+
window.PWADeferredPrompt.userChoice
12+
.then(function (choiceResult) {
13+
window.PWADeferredPrompt = null;
14+
});
15+
}
16+
}
17+
};

Blazor.PWA.MSBuild.Tasks/Templates/ServiceWorker/sw_register.template.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,3 @@
2424
}
2525
});
2626

27-
window.addEventListener('beforeinstallprompt', function (e) {
28-
// Prevent Chrome 67 and earlier from automatically showing the prompt
29-
e.preventDefault();
30-
// Stash the event so it can be triggered later.
31-
window.PWADeferredPrompt = e;
32-
33-
showAddToHomeScreen();
34-
35-
});

Blazor.PWA.MSBuild.Tasks/build/BlazorPWA.MSBuild.ServiceWorkerRegister.targets

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,30 @@
2020
<ServiceWorkerRegisterInstallableType Condition="'$(ServiceWorkerRegisterInstallableType)'==''">installable-banner</ServiceWorkerRegisterInstallableType>
2121
<!-- The file that contains template code for the service worker "update available" -->
2222
<ServiceWorkerRegisterInstallableTemplate Condition="'$(ServiceWorkerRegisterInstallableTemplate)' == ''">$(ServiceWorkerRegisterTemplatePath)sw_register-$(ServiceWorkerRegisterInstallableType).template.js</ServiceWorkerRegisterInstallableTemplate>
23+
<!-- Before Install Prompt handler type -->
24+
<ServiceWorkerRegisterBeforeInstallPromptType Condition="'$(ServiceWorkerRegisterBeforeInstallPromptType)'==''">beforeinstallprompt</ServiceWorkerRegisterBeforeInstallPromptType>
25+
<!-- The file that contains template code for the service worker register "beforeinstallprompt" -->
26+
<ServiceWorkerRegisterBeforeInstallPromptTemplate Condition="'$(ServiceWorkerRegisterBeforeInstallPromptTemplate)' == ''">$(ServiceWorkerRegisterTemplatePath)sw_register-$(ServiceWorkerRegisterBeforeInstallPromptType).template.js</ServiceWorkerRegisterBeforeInstallPromptTemplate>
2327
<!-- event fired by browser when the service worker has installed ** probably never change **-->
2428
<ServiceWorkerInstalledEvent Condition="'$(ServiceWorkerInstalledEvent)'==''">installed</ServiceWorkerInstalledEvent>
2529
<!-- Text to display when an update is available -->
2630
<ServiceWorkerUpdateAlertText Condition="'$(ServiceWorkerUpdateAlertText)'==''">Update available. Reload the page when convenient.</ServiceWorkerUpdateAlertText>
31+
<!-- The Blazor namespace for callbacks -->
32+
<ServiceWorkerBlazorAssembly Condition="'$(ServiceWorkerBlazorAssembly)'==''">$(ProjectName)</ServiceWorkerBlazorAssembly>
33+
<!-- The Blazor method to call when a PWA is installable -->
34+
<ServiceWorkerBlazorInstallMethod Condition="'$(ServiceWorkerBlazorInstallMethod)'==''">PWAInstallable</ServiceWorkerBlazorInstallMethod>
2735
<!-- Setup the declarations for the Service Worker Register -->
28-
<ServiceWorkerRegisterConstants Condition="'$(ServiceWorkerConstants)' == ''">
36+
<ServiceWorkerRegisterConstants Condition="'$(ServiceWorkerRegisterConstants)' == ''">
2937
const serviceWorkerFileName = '$(ServiceWorkerBaseURL)$(ServiceWorkerFileName)'%3B;
3038
const swInstalledEvent = '$(ServiceWorkerInstalledEvent)'%3B;
3139
const staticCachePrefix = '$(ServiceWorkerCacheName)-v'%3B;
3240
const updateAlertMessage = '$(ServiceWorkerUpdateAlertText)'%3B;
3341
</ServiceWorkerRegisterConstants>
42+
<ServiceWorkerRegisterConstants Condition="'$(ServiceWorkerRegisterInstallableType)' == 'installable-blazor'">
43+
$(ServiceWorkerRegisterConstants);
44+
const blazorAssembly = '$(ServiceWorkerBlazorAssembly)'%3B;
45+
const blazorInstallMethod = '$(ServiceWorkerBlazorInstallMethod)'%3B;
46+
</ServiceWorkerRegisterConstants>
3447
</PropertyGroup>
3548
<ItemGroup>
3649
<!-- Read the Service Worker Register template-->
@@ -45,7 +58,24 @@
4558
<ServiceWorkerRegisterTemplateLines
4659
Condition="Exists('$(ServiceWorkerRegisterInstallableTemplate)')"
4760
Include="$([System.IO.File]::ReadAllText($(ServiceWorkerRegisterInstallableTemplate)))"/>
61+
<!-- Read the Service Worker Register Before Install Prompt template-->
62+
<ServiceWorkerRegisterTemplateLines
63+
Condition="Exists('$(ServiceWorkerRegisterBeforeInstallPromptTemplate)')"
64+
Include="$([System.IO.File]::ReadAllText($(ServiceWorkerRegisterBeforeInstallPromptTemplate)))"/>
4865
</ItemGroup>
66+
<!-- Debugging -->
67+
<Message
68+
Importance="high"
69+
Text="Service Worker Register Template: $(ServiceWorkerRegisterTemplate)"/>
70+
<Message
71+
Importance="high"
72+
Text="Service Worker Update Template: $(ServiceWorkerRegisterUpdateTemplate)"/>
73+
<Message
74+
Importance="high"
75+
Text="Service Worker Installable Template: $(ServiceWorkerRegisterInstallableTemplate)"/>
76+
<Message
77+
Importance="high"
78+
Text="Service Worker Before Install Prompt Template: $(ServiceWorkerRegisterBeforeInstallPromptTemplate)"/>
4979
<!-- (Re)Create the ServiceWorkerRegister.js file -->
5080
<WriteLinesToFile
5181
Condition="'$(ServiceWorkerRegisterConstants)@(ServiceWorkerRegisterTemplateLines)' != ''"

CHANGELOG.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
#### 10/08/2019
1+
#### 10/08/2019 - 13/08/2019
22

33
- Added new **Property** **`ServiceWorkerUpdateAlertText`** - used to change the default text in the "Update available alert".
44
- Added new **Property** **`ServiceWorkerRegisterUpdateType`** - used to select the "Update available alert" type.
55
- Added new **Property** **`ServiceWorkerRegisterUpdateTemplate`** - The name of the template file for the "update available" event.
6-
- Moved the "Updated available alert" to it's own template so we can have alternates
76
- Added new **Property** **`ServiceWorkerRegisterInstallableType`** - used to select the "Installable PWA alert" type.
87
- Added new **Property** **`ServiceWorkerRegisterInstallableTemplate`** - The name of the template file for the "Installable PWA alert" event.
8+
- Added new **Property** **`ServiceWorkerRegisterBeforeInstallPromptType`** - used to select the "Before Install Prompt" type.
9+
- Added new **Property** **`ServiceWorkerRegisterBeforeInstallPromptTemplate`** - The name of the template file for the "Before Install Prompt" event.
910
- Moved the "Update available alert" to it's own template so we can have alternates
1011
- Moved the "Installable PWA alert" to it's own template so we can have alternates
11-
- Tidied up default install alert
12+
- Moved the "Before Install Prompt" to it's own template so we can have alternates
13+
- Tidied up default install alert a bit
14+
- Added new Installable PWA Alert type - "installable-blazor" which will call a Blazor static method, which is defined by:
15+
- **`ServiceWorkerBlazorAssembly`** which is used to define the Blazor Assembly namespace - defaults to the project name
16+
- **`ServiceWorkerBlazorInstallMethod`** which is used to define the Blazor method to call - defaults to 'PWAInstallable'
17+
- Updated README with Blazor interop example.
1218

1319
#### 08/08/2019 Initial Release

README.md

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,75 @@ The web manifest has properties for the application name, which are taken, by de
5959

6060
There are dozens of Properties in the *targets* files supplied by this package - you *could* customise them all, but you probably don't need to, so proceed with caution.
6161

62+
### Handing the "Installable PWA" event in Blazor
63+
64+
When a Chromium based browser detects an installable PWA, it fires an event that your application
65+
can use to display a prompt to the user.
66+
67+
The default for this package is to simply display a bar at the bottom of the
68+
browser window prompting the user to install your app.
69+
70+
You can customise the message, or you can override that and pass the event
71+
over to your Blazor application - by setting the property
72+
**`ServiceWorkerRegisterInstallableType`** to **`installable-blazor`** in your **.csproj**
73+
74+
This will generate code in your **ServiceWorkerRegister.js** to make an
75+
interop call to Blazor when the **beforeinstallprompt** event fires in the browser.
76+
77+
In your Blazor application, you will need code to handle this call
78+
- by default it will use the **ProjectName** and method name **"InstallPWA"** to
79+
perform a `DotNet.InvokeAsync()`
80+
- These values can be customised using the properties **`ServiceWorkerBlazorAssembly`**
81+
and **`ServiceWorkerBlazorInstallMethod`**
82+
83+
Here is an example Blazor implementation which could be added to **MainLayout**
84+
85+
``` HTML
86+
@if (Installable)
87+
{
88+
<div class="fixed-bottom w-100 alert alert-dark d-flex align-items-center" @onclick="InstallClicked">
89+
<h3>Install this app?</h3>
90+
<small class="ml-auto mr-1 rounded-pill"><button @onclick="@(()=>Installable=false)">X</button></small>
91+
</div>
92+
}
93+
```
94+
This will display a bar at the bottom of the browser, which can be dismissed or clicked.
95+
96+
97+
The `code` section has the `JSInvokable` method **InstallPWS** that we called
98+
earlier from the browser and some supporting code to toggle the display and
99+
make an interop call back to the browser to trigger the app installation.
100+
``` C#
101+
@code
102+
{
103+
[Inject] IJSRuntime JSRuntime { get; set; }
104+
105+
static bool Installable = false;
106+
static Action ml;
107+
protected override void OnInitialized()
108+
{
109+
ml = () => InvokeAsync(StateHasChanged);
110+
}
111+
[JSInvokable]
112+
public static Task InstallPWA()
113+
{
114+
Installable = true;
115+
ml.Invoke();
116+
return Task.CompletedTask;
117+
}
118+
Task InstallClicked(UIMouseEventArgs args)
119+
{
120+
Installable = false;
121+
return JSRuntime.InvokeAsync<object>("BlazorPWA.installPWA");
122+
}
123+
}
124+
```
125+
62126
## Roadmap
63127

64128
- [ ] At the moment, there is only one choice for caching strategy - Cache First/Network Fallback - I will add more (https://developers.google.com/web/ilt/pwa/introduction-to-progressive-web-app-architectures#caching_strategies_supported_by_sw-toolbox)
65-
- [ ] The current methods for alerting the user are semi-hard coded (you can adjust them manually after generation) - this will change to allow hooks/callbacks into Blazor via project properties
129+
- [x] The current method for alerting the user that the app is installable is semi-hard coded (you can adjust it manually after generation) - this will change to allow hooks/callbacks into Blazor via project properties
130+
- [ ] The current method for alerting the user when an update is available is semi-hard coded (you can adjust it manually after generation) - this will change to allow hooks/callbacks into Blazor via project properties
66131
- [ ] Document all of the configuration Properties (they all have comments in the code - so you are able to understand their purpose without documentation...)
67132
- [ ] Bug fixes
68133

0 commit comments

Comments
 (0)