Telerik blogs
Javascript

PWAs enable you to take an existing web app and offer it as a native mobile experience. In this blog, we showcase how to do so using our sample travel app, TripXpert.

It's hard to escape all the hype around Progressive Web Apps (PWAs). Everyone involved with the web is excited about the prospect of being able to take their existing web applications and offer them in a more native way on mobile devices. While many of you may have taken a look at what they are and have a good idea of what the characteristics of a PWA might be, what you may not know is that we have a couple of PWA examples using Kendo UI components.

Today, I wanted to dive into one of these applications, TripXpert, which we have showcased as a responsive web app for quite some time. Why bring this up in a PWA conversation? Well, recently the team took time to make TripXpert a full-blown PWA and I wanted to highlight just how we went about adding in this extra functionality.

Wait a Second, What is a PWA?

In case you haven't yet heard about Progressive Web Apps I'll get you caught up in just a paragraph or two.

The quick and dirty around PWAs is that they are responsive web applications that can take advantage of mobile devices' native features. You might initially say that this is close to Cordova and hybrid applications of old, but the main difference is that there is no need to run the application in a shell, or offer plugins or wrappers for native functionality. A PWA is a regular web app that we browse to with our desktop and mobile devices today. We just sprinkle in some extra capabilities on mobile devices when a user opts to add them to the home screen, which is done through the browser rather than the various app stores.

I should mention that beyond just adding native mobile functionality to an app, a PWA is also an optimized application that is reliable and can load instantly even under slow or no internet connections. It is also fast and can provide a native-like experience (responds quickly to interactions, has smooth animations and is devoid of "jank"). Finally, a PWA should also be engaging, which means that it should feel like a natural app on the mobile device—including all of the UX that comes with that mindset. So, it's not quite as easy as just adding in a new setting and getting a PWA, you have to make sure that you're properly optimized to be a PWA. The end result is good for you either way though, as not only are you getting further engagement in your application thanks to mobile users, you also end up having a faster application, which is very important for today's web apps.

Google has a great PWA page that covers everything at a high level, and also provides in-depth links and more resources to explore. It covers what I mentioned above and way more, plus has tools like Lighthouse to help with building and optimizing your PWA, so definitely check it out!

Actually, What is "TripXpert"?

Let me also introduce the application that we'll be talking about throughout this article: TripXpert. Essentially, TripXpert is built to mimic a tourism website that allows you to book complete vacation packages to various locations across the globe. What I like about this scenario is that it gives us a lot of real-world examples to deal with, like displaying a list of packages, creating an enticing layout for each package, providing a detail view that can incorporate our data visualization components and maps, and a few other things. Additionally, from a mobile perspective, users could be looking at potential trips while on the go, making this a great scenario to start taking advantage of device features and create a great PWA.


tripxpert-in-action

TripXpert was built with ASP.NET MVC and the Telerik UI for ASP.NET MVC components. The great thing about these components is that they are built on top of the jQuery edition of the Kendo UI components, so anything I mention in this article can be applied to jQuery, ASP.NET MVC, and ASP.NET Core (also based on our jQuery widgets)!

Here's a full list of the components we used:

  • DropDownList
  • MaskedTextBox
  • Window
  • Button
  • Map
  • Dialog
  • Menu
  • ListView
  • Calendar
  • TabStrip
  • ComboBox
  • ResponsivePanel
  • Kendo UI Validator

At first, this application was meant to showcase the great-looking UI components that we have (just a straight up brag, I know, but I'm biased :wink:), along with putting them together in a larger sample, but it also took on a vital role of showcasing how to build a responsive web app using our UI components. The Kendo UI components are all responsive out of the box, which means that outside of some design choices we've made for the look-and-feel of the app itself, any kind of responsive design approaches will fully work with our components.

Now, to make this a PWA, there are a couple of things that we need to do on top of the responsive features, but I'll get to that a little bit later. First, let's cover what we did around responsiveness.

Making Things Responsive

To make sure that we follow patterns that you might be using today, we started off with adding in Bootstrap v4.1.1. While we do not use it 100% across all of our responsive features (some of that is driven by custom CSS we implemented or classes available in the Kendo UI themes) you'll see plenty of inclusions of the Boostrap spacing utilities sprinkled throughout the HTML. This helps us ensure that things appear in the right places depending on the viewport that we have available, which is very important as the real estate of the application changes—we don't want elements to either appear squished next to each other or weirdly stacked when we resize the browser (especially when we use a mobile device).

Bootstrap isn't the only thing we used though. There's also some work done coming from the Kendo UI jQuery components and our integration with Bootstrap that we can take advantage of to make things truly responsive.

While I can't go through exactly every piece of responsiveness (that would be an article in its own), I can provide a couple of highlights.

Looking over the search area, we can see that we have different views of search depending on our view port.

Desktop


tripxpert-search-desktop

Mobile


tripxpert-search-mobile

As we can see, we drop a lot of extra "fluff" around searches when we get down to a smaller screen size. You can still select the filters that we have in the beginning based on interactions with the search input, but from a real estate perspective, we cut down quite a bit.

This is all handled by working with Bootstrap to set up some rules around when to display a certain element, having worked with the Bootstrap Display Utilities to decide at what size we should hide these inputs. Then, from a Kendo UI component perspective, we simply define that we want the element to have width:100% like we do here:

 

@(Html.Kendo().DropDownList()
    .Name("OfferType")
    .OptionLabel("Offer type")
    .DataTextField("Text")
    .DataValueField("Value")
    .BindTo(new List<SelectListItem>() {
        new SelectListItem() { Text = "All types", Value = "All" },
        new SelectListItem() { Text = "Special offers", Value = "Special" },
        new SelectListItem() { Text = "Regular offers", Value = "Regular" }
    })
    .HtmlAttributes(new { style = "width:100%;", @class = "tx-offertype" })
    .Height(150)
    .Events(x=> x.Change("onOfferTypeChange"))
)

Notice the .HtmlAttributes configuration option above—this is more or less all we need for Kendo UI to adhere to whatever rules we set up with Bootstrap!

The above is just an extract of what we've done to add responsiveness to TripXpert. There's plenty of more responsive features built in (check out how the ListView goes from multiple columns down to a single column on mobile devices) but I want to jump over to what has been done

Let's Make a PWA!

Now, just making a website responsive is really just the first step. How do we take something like TripXpert and make it a full-blown PWA? Well, there are a few things that we have to do on a technical level to "PWA-ify" our application. From a UI perspective (the app has to follow UX guidelines for PWAs afterall), we're luckily covered due to a few reasons, one being the usage of the Kendo UI CSS classes that we can pull out from our themes. Having built-in responsiveness from the Kendo UI components helps out quite a bit as well of course!

Let's take a look at the two things that immediately stick out to take this application from responsive to progressive.

Adding the manifest.json

 

The manifest.json file describes what your application is and how it should behave when a user installs the application to their home screen. Browsers can read this file and get a quick rundown of what to expect from your app. This file is required in order tor browsers like Chrome to provide the "Add to Home Screen" option for your application (which we will see in action later). For a more thorough explanation of what the manifest.json file is, please refer to Google's Web Fundamentals guide around The Web App Manifest.

For reference, here's what we specifically did for TripXpert and its manifest.json file:

 

{
  "name": "TripXpert",
  "short_name": "TripXpert",
  "theme_color": "#858585",
  "background_color": "#858585",
  "display": "standalone",
  "Scope": "./",
  "lang": "en",
  "description": "The ultimate trip planning app!",
  "start_url": "./",
  "icons": [
    {
      "src": "/Images/App/Icons/HomeScreen/homescreen-icons_144X144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "/Images/App/Icons/HomeScreen/homescreen-icons_180X180.png",
      "sizes": "180x180",
      "type": "image/png"
    },
    {
      "src": "/Images/App/Icons/HomeScreen/homescreen-icons_192X192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/Images/App/Icons/HomeScreen/homescreen-icons_512X512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "splash_pages": null,
  "screenshots": [
    {
      "src": "/Images/Gallery/France/Eiffel-Tower_Attraction.jpg",
      "sizes": "320x320",
      "type": "image/jpeg"
    },
    {
      "src": "/Images/Gallery/Italy/Roman-Colosseum_Lilia-Karakoleva.jpg",
      "sizes": "320x320",
      "type": "image/jpeg"
    }
  ]
}


What we see here is the name of the application, TripXpert, along with the icons that we would like to have associated with the application depending on the size that the device offers. We also set up things like ensuring that the application always starts on ./, even if the user saved the application to their desktop when navigating a particular vacation package, and we also define that it should run as a standalone application so it runs in its own window, rather than in the standard browser UI. This helps us make the user experience feel even closer to a native app.

Hard Labor with Service Workers

 

Beyond just describing how our application should behave when installed, we want to take advantage of some actual native functionality. This includes potentially working with the application offline, syncing data in the background, push notifications and much more! The key to this is service workers and they are what make PWAs bridge the gap between web and native functionality.

For the scope of this article, a service worker is written in JavaScript and the browser runs it in the background, separate from your web application. It cannot access the DOM directly, so you have to handle communication between the service worker and your business logic. A service worker also terminates itself when its not used, and starts up again when it is used, so you can't use service workers themselves to maintain state and data.

For more information on service workers (there's a ton to look in to!), I recommend Google's Service Workers: an Introduction article, which does a great job of diving in to what service workers are and how to start using them in your applications.

The service worker file for TripXpert is a bit long, so rather than pasting it here, I'll link directly to service-worker.js right here.

What you'll notice initially is a variable that we set up to hold files that we want to be cached:

var cachedFiles = [
    path,
    path + "bundles/css?v=9bf_1hN1hPPO3xXNTcN-zh4IFDRFrfVrnaYTdddcBHQ1",
    path + "bundles/js?v=3NJpYh6kMRRulVfyFMXyChUMveyhk6-RqaWX_5pxkQY1",
    path + 'Images/App/TripXpert_logo.svg',
    path + 'Home/GetDestinations',
    path + 'Home/GetSpecialDestinations',
    path + 'Destinations/Destinations_First',
    path + 'Images/Gallery/Barcelona-and-Tenerife/Arc-de-Triomf,-Barcelona,-Spain_Liliya-Karakoleva.JPG?width=320&height=320',
    [...]


This part, especially the first couple of lines, is important to ensure that we keep all of the functionality of our application intact when we install our application to a mobile device. This variable provides all of the paths to JavaScript files that we need to use in our application, and then transitions into pages, images and other files that we need to have in our application.

We use this when the install event of the service worker triggers (when we actually install the application to a device) to actually cache all of the files provided in the cachedFiles array.


self.addEventListener('install', function (e) {
    e.waitUntil(
        caches.open(cacheName).then(function (cache) {
            Promise.all(
                cachedFiles.map(function (url) { cache.add(url) })
            );
        })
    );
});


Now, once we've installed and set up our cached files, we will actually have to handle what happens when requests are made within the application. A service worker will start receiving fetch events when a user navigates to a different page or refreshes a particular page. This is our chance to check if we have something available in the cache, or if we need to retrieve something outside of the application. If it is a resource that we have available locally, let's not waste time and bandwidth by trying to retrieve it from a server somewhere!

I won't go in to a full rundown of what each line means here, but for reference the way we handle this in TripXpert is the following.

self.addEventListener('fetch', function (e) {
    if ((e.request.url.indexOf("kendo") === -1 && e.request.url.indexOf("TripXpert") === -1) ||
        e.request.url.indexOf("t.eloqua") !== -1 ||
        e.request.url.indexOf("d.company") !== -1 ||
        e.request.url.indexOf("facebook") !== -1 ||
        e.request.url.indexOf("google") !== -1 ||
        e.request.url.indexOf("outbrain") !== -1 ||
        e.request.url.indexOf("twitter") !== -1 ||
        e.request.url.indexOf("linkedin") !== -1 ||
        e.request.url.indexOf("Destination") !== -1
    ) {
        return;
    }
 
    e.respondWith(
        caches.match(e.request.url).then(function (resp) {
            return resp || fetch(e.request.url).then(function (response) {
                var clonedResponse = response.clone();
                 
                if (response.redirected) {
                    return new Response(response.body);
                }
 
                caches.open(cacheName).then(function (cache) {
                    cache.put(e.request.url, clonedResponse);
                });
                return response;
            });
        }).catch(function () {
            return caches.match('/');
        })
    );
});


With all of the above, we have the fundamentals of a PWA. Our application can now be installed, it will look like a native application (no browser UI getting in the way), have its own set of icons on various home screens and will work offline without any missing functionality! We aren't adding push notifications or anything like that in this iteration, but this gives us a good baseline for an initial PWA that we can of course expand upon to make even more native-like!

The Proof is in the Pudding

Let's see what this looks like on my mobile device!


tripxpert-mobile-gif

As we can see, on this Android device, Chrome offers me the ability to save this app to my phone's home screen. Now, you'll notice that I get a generic icon rather than this beautiful icon which seems to be due to my custom launcher. For any of you trying this on your own devices, you should be able to see the TripXpert icon instead.

Once I open up the application, we can see that there really isn't a way to tell that this is not a regular mobile application. There is no browser UI bar at the bottom, or any kind of search bar at the top, so from a user's perspective, we've created something that looks great on their device and they access it just like they would any other installed app they picked up from the App Store or Google Play.

The Most Important Bit: Source Code

I've been linking to the actual TripXpert application so far, but what about the source code? It is found right here on GitHub and will give you even further insight on how we set things up (server-side code included)! This is a great way to gain more insight in to just how we created this lovely little application.

Pssst—We Have Other PWAs!

By the way, TripXpert is not the only sample that we have that covers how to build a Progressive Web Application! If you're interested in seeing how this can be done in Angular or React, then I have some great news for you: we have sample PWAs built for these frameworks as well! Check them our right here:

Final Notes

While this blog post didn't take you step-by-step through building TripXpert, it hopefully gave you some ideas and guidance around how you can potentially take your own applications and make them into a PWA. There might be a few steps here, including making your app actually responsive, but once you've got a great layout and user experience for mobile devices, it is only natural to start adding in some more features to make the experience that much better for mobile users! Go forth and PWA!


Carl Bergenhem
About the Author

Carl Bergenhem

Carl Bergenhem was the Product Manager for Kendo UI.

Related Posts

Comments

Comments are disabled in preview mode.