OVERVIEW
This is a guideline with code snippets to create a mobile-only PWA Installation Panel without workflow.
Step by step
1. Create a new row with Custom Code Object
2. Paste the following codes into different tabs as indicated:
For HTML
<div id="pwa-banner">
<div id="chrome-content" class="main-content">
<img class="app-icon" src="placeholder-icon.png" alt="App Icon">
<div class="info">
<div class="app-name">My PWA App</div>
<div class="app-desc">Install this app on your device</div>
</div>
<button id="install-btn">Install</button>
</div>
<div id="ios-content" class="ios-message">
<img class="app-icon" src="placeholder-icon.png" alt="App Icon">
<div class="info">
<div class="app-name">My PWA App</div>
<div class="app-desc">
<ol>
<li>Tap the <strong>Share</strong> button at the bottom of Safari</li>
<li>Choose <strong>Add to Home Screen</strong>.</li>
</ol>
</div>
</div>
<button id="ios-close-btn" class="ios-close-btn">Got it</button>
</div>
</div>
For CSS
#pwa-banner {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%) translateY(-200%);
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(8px);
border-radius: 16px;
border: 1px solid #ccc;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
display: none;
align-items: center;
padding: 12px 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
max-width: 420px;
width: 80%;
transition: transform 0.4s ease, opacity 0.4s ease;
opacity: 0;
flex-direction: column;
text-align: center;
}
#pwa-banner.show {
transform: translateX(-50%) translateY(0);
opacity: 1;
display: flex;
}
#pwa-banner .main-content {
display: flex;
align-items: center;
width: 100%;
display: none;
padding: 0;
}
#pwa-banner .main-content.show-flex,
#pwa-banner .ios-message.show-flex{
display: flex;
}
#pwa-banner img {
width: 48px;
height: 48px;
border-radius: 12px;
margin-right: 12px;
}
#pwa-banner .info {
flex: 1;
text-align: left;
}
#pwa-banner .info .app-name {
font-weight: bold;
margin-bottom: 5px;
}
#pwa-banner .info .app-desc {
font-size: 0.85rem;
color: #555;
ol {
margin-block: 0px;
padding-inline-start: 16px;
}
}
#pwa-banner button {
background: transparent;
color: #3297fd;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 0.9rem;
}
/* --- iOS Specific Styles --- */
.ios-message {
display: flex;
align-items: flex-start;
width: 100%;
display: none;
padding: 0;
}
.ios-instruction {
text-align: left;
display: flex;
flex-direction: column;
}
.ios-close-btn {
padding: 6px 16px;
}
For JS Preload
var WEBSITE_URL = "www.mhtc.org.my";
var ENABLE_DISMISS = false;
For JS Execute
/*global $, moment, fetch, WEBSITE_URL, ENABLE_DISMISS, $x*/
/*jslint browser, devel, multivar, long*/
(function () {
"use strict";
var MANIFEST_URL = "manifest.json";
var deferredPrompt;
// jQuery selectors
var $banner = $("#pwa-banner");
var $installBtn = $("#install-btn");
var $chromeContent = $("#chrome-content");
var $iosContent = $("#ios-content");
var $iosCloseBtn = $("#ios-close-btn");
var $appIcon = $(".app-icon");
var $appNameEl = $(".app-name");
var $appDescEl = $(".app-desc");
var BANNER_KEY = "pwa_banner_dismissed";
var INSTALL_KEY = "pwa_installed";
var BANNER_REMIND_DAYS = 3;
// --- Utility Functions ---
function isIOS() {
var userAgent = window.navigator.userAgent.toLowerCase();
return /iphone|ipad|ipod/.test(userAgent);
}
function isMobileOrTablet() {
var maxScreenWidth = 1024;
var isSmallScreen = window.innerWidth <= maxScreenWidth || window.screen.width <= maxScreenWidth;
// var hasTouch = (window.ontouchstart !== undefined) || (navigator.maxTouchPoints > 0);
return isSmallScreen;
}
function isInStandaloneMode() {
return localStorage.getItem(INSTALL_KEY) === "true" ||
window.matchMedia("(display-mode: standalone)").matches ||
(window.navigator.standalone === true);
}
function shouldShowBanner() {
if (!isMobileOrTablet()) {
return false;
}
if (isInStandaloneMode()) {
return false;
}
if (ENABLE_DISMISS) {
var lastDismiss = localStorage.getItem(BANNER_KEY);
if (!lastDismiss) {
return true;
}
var lastDismissTime = parseInt(lastDismiss, 10);
var now = Date.now();
var daysSinceDismiss = (now - lastDismissTime) / (1000 * 60 * 60 * 24);
return daysSinceDismiss >= BANNER_REMIND_DAYS;
} else {
return true;
}
}
function getManifestDetails() {
return fetch(MANIFEST_URL)
.then(function (response) {
return response.json();
})
.then(function (manifest) {
var icon = manifest.icons.find(function (icon) {
var size = parseInt(icon.sizes.split("x")[0], 10);
return icon.sizes === "192x192" || size >= 192;
}) || manifest.icons[0];
return {
name: "Install " + (manifest.short_name || manifest.name || "My App"),
iconUrl: icon
? icon.src
: "placeholder-icon.png",
description: WEBSITE_URL
};
})
.catch(function (error) {
console.error("Error fetching manifest:", error);
return {
name: "My App",
iconUrl: "placeholder-icon.png",
description: "Install this app on your device"
};
});
}
// --- Banner Display Logic ---
function hideBanner() {
$banner.removeClass("show");
setTimeout(function () {
$chromeContent.removeClass("show-flex");
$iosContent.removeClass("show-flex");
$banner.hide();
}, 400);
}
function showBanner(platform) {
if (!shouldShowBanner()) {
return;
}
$chromeContent.removeClass("show-flex");
$iosContent.removeClass("show-flex");
if (platform === "chrome") {
getManifestDetails().then(function (details) {
$appIcon.attr("src", details.iconUrl);
$appNameEl.text(details.name);
$appDescEl.text(details.description);
$chromeContent.addClass("show-flex");
$banner.css("display", "flex");
$banner.addClass("show");
});
} else if (platform === "ios") {
getManifestDetails().then(function (details) {
$appIcon.attr("src", details.iconUrl);
$appNameEl.text(details.name);
$iosContent.addClass("show-flex");
$banner.css("display", "flex");
$banner.addClass("show");
});
}
}
function dismissBanner() {
hideBanner();
localStorage.setItem(BANNER_KEY, Date.now().toString());
console.log("Banner dismissed, will remind again in " + BANNER_REMIND_DAYS + " days.");
}
// --- Event Listeners ---
$(window).on("beforeinstallprompt", function (e) {
e.originalEvent.preventDefault();
deferredPrompt = e.originalEvent;
showBanner("chrome");
});
$(window).on("appinstalled", function () {
localStorage.setItem(INSTALL_KEY, "true");
hideBanner();
deferredPrompt = null;
console.log("PWA was successfully installed.");
});
$installBtn.on("click", function () {
if (deferredPrompt) {
hideBanner();
deferredPrompt.prompt();
deferredPrompt.userChoice.then(function (choiceResult) {
console.log("User choice:", choiceResult.outcome);
deferredPrompt = null;
localStorage.setItem(BANNER_KEY, Date.now().toString());
});
}
});
$iosCloseBtn.on("click", dismissBanner);
$(window).on("click", function (e) {
var isBannerVisible = $banner.hasClass("show");
var isClickInsideBanner = $banner.is(e.target) || $banner.has(e.target).length > 0;
if (isBannerVisible && !isClickInsideBanner) {
dismissBanner();
}
});
$(window).on("load", function () {
if (isIOS() && !isInStandaloneMode()) {
showBanner("ios");
} else if (deferredPrompt) {
showBanner("chrome");
}
});
}());