Offline Interaction Design Reconsidered
After implementing and deploying several offline apps, I discovered that many user were confused by the interaction design proposed in the previous lecture, Design for Offline. User were confused by the meaning of the “save” and “upload” buttons. Users would ask, “When do I save and when do I upload the data?” Although the users were aware when they might be offline, they did not understand that the “save” button would save the data to the device and that “upload” would submit the data to server. Users understand that a website saves data on the sever. They are not aware that HTML5 allows websites to save data on their device. Consequently, they do not understand the need two different saves.
Zack Duford, a former student, proposed new offline interaction. Zack’s interaction design eliminates the “save” button. Weather online or offline, the button for saving the data is “upload” (or “submit”). If the app is online then the app automatically saves the data to the server. If the app is offline the app saves the data on the device and displaces the data in a “queue”. While offline, the user can view the “queue” which shows all the data already collected offline. Consequently, users can view the queue receive feedback that the data “will be uploaded”. Zack proposed that user should not be permitted to edit the data in the queue. At the time, Zack and I thought that HTML5 Service Workers would be able to detect online status and automatically upload the data in the queue to the server.
Zack made a prototype of a fishing app using Adobe XD. Fishermen can record catches that make out on lake on offline entering the location, description and photo of the catch. You can view screen shoots of Zack’s prototype in Zack’s Offline Fishing App Views. During the usability test, Zack tried to ascertain the participants mental model of the app by asking two questions after each test scenario:
- Is this a satisfactory interface for the interaction?
- Do you think you are being provided sufficient information about the data at any given point in time in the scenario?
Zack tested 7 participants, 3 CS majors and 4 non-CS majors. Only one non-CS major participant show some confusion by the questions. You can read Zack’s Offline Design Usability Report and Zack’s Usability Test Plan for Offline Fishing App.
Later, I discovered that the Service Workers cannot automatically upload the app’s data when an internet connection is established. After an internet connection is established, the user must interact with the app and click a “submit” or “upload” button. Nevertheless this does not invalidate all of Zack’s design. The queue does give the user concrete and visual display of where the data is and that it ready for uploading.
Single Page Application and Service Workers
In my previous lecture about Offline Implementation, my students and I describe an architecture for the offline component of the app that mirrors the online design pattern, meaning Model-View-Controller design pattern. The offline line component of the app had separate views that mirrored the online views and JavaScript files that assumed the role of controllers of respective controller. Finally, they were JavaScript classes that assumed the role of domain objects in online component. I proposed this architectural design because I felt this would allow developers to first develop the online app using well established frameworks, e.g. Grails. But the architectural design was awkward to maintain and inefficient. Both the offline and online views were very similar and change in one view would frequently require change the corresponding view. Also changing views would require retrieving the new view from cache.
Recently, industry proposes an new architectural for offline app, Single Page Application (SPA). In a SPA, there is only one html or gsp file. The JavaScript code of the app control or simulate different views by hiding and showing divs. Developer has only a single code base to manage. JavaScript runs very fast and render the page quickly.
A new HTML5 standard, Service Worker, will make App Cache obsolete. The very humorous blog, Application Cache is a Douchebag, illustrates the many problems with app cache. Service workers are more flexible, but they are still not implemented in the iOS and Edge. Although the app cache is limited, for our applications it suffices, but implementing offline caching using service workers is not difficult for our applications once the initial configuration is set up.
Jake Mager, a former student, developed a tutorial demonstrating implementing an offline SPA for the fishing app. To learn from Jake’s tutorial clone the repository, fishing-tutorial, onto your development machine. Import the source code into IntelliJ. Be sure that the SDK used by IntelliJ is Java 8. In the repository you find a Tutorial directory with tutorial.html. Right click tutorial.html and select Open in Browser. Follow along with the tutorial developing the app in another project.
Jake uses only JQuery show and hide functions to implement the view changes in the SPA:
$("#showCatchesButton").hide() $("#editCatchButton").show();
The views are primarily generated by the index.js file.
Grails domain object are still used as Object Relational Model (ORM) to interface between the business code on the server and the database. Controller methods represent endpoints for AJAX calls made by JavaScript code.
The tutorial also explains how to implement service workers in Grails for caching the app. In particular the grails resource pattern must be configured:
grails: resources: pattern: '/**'
The service worker file in src/main/webapp/sw.js is simple:
self.addEventListener('install', function (event) { self.skipWaiting(); event.waitUntil( caches.open('fishingApp').then(function (cache) { return cache.addAll( [ '/', '/assets/jquery-3.1.1.js?compile=false', '/assets/index.css?compile=false', '/assets/main.css?compile=false', '/assets/bootstrap.css?compile=false', '/assets/bootstrap-theme.css?compile=false', '/assets/bootstrap.js?compile=false', '/assets/index.js?compile=false', '/assets/application.js?compile=false', '/assets/logo.png', '/assets/localforage/localforage.js?compile=false', '/assets/localforage/localforage.min.js?compile=false', '/assets/localforage/localforage.nopromises.js?compile=false', '/assets/localforage/localforage.nopromises.min.js?compile=false', ] ); }) ); console.log('Install event:', event); });
The service worker is registered after the document has loaded in index.js
$(document).ready(function() { // check if user is online with navigator if (navigator.onLine) { // check if a service worker is supported then register it if ('serviceWorker' in navigator) { navigator.serviceWorker .register('/sw.js') .then(function() { console.log("Service Worker Registered"); }); } ... }
React JavaScript Framework
There are several JavaScript framework for developing single page applications. For example:
- React – https://reactjs.org/
- Angular – https://angular.io/
- Ember – https://www.emberjs.com/
- Vue.js – https://vuejs.org/
React, in particular, is effective for SPA development because it is very light weight and responsive. It is also support by Facebook. Although Angular is also supported by a large company, Google, React is a simpler framework, and I think has a shorter learning curve. React is unique from the other JavaScript frameworks because it moves the HTML code into JavaScript code. Combining JavaScript and HTML code in the same file does invalidate “division of concerns”, specifically visual from logic. React recognizes the coupling of logic with the UI. React uses JSX to express HTML in the JavaScript code:
https://reactjs.org/docs/introducing-jsx.html
Instead of making the separation of concern between visual (or layout) and logic, using React with JSX separates concerns between components of the UI so that the logic associated with the view component are together in one file. For software engineering developers at MTU, separation by component view is better because the developers implement both the visual presentation and the logic of the view.
The structure of the react components is hierarchical just like the DOM:
https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction
Components of the app communicate by passing down properties, called props, down the component hierarchy. Props that are objects send messages down the component hierachy, and properties that are functions for event listener send messages up the component hierarchy.
React introduces another concept, the application state. The state of the application is expressed as as object of a property of a component class. Ideally, there is only one state object for the entire web app, or a major portion of the app. This is advantageous because there is a single place of truth. It also facilitate the responsiveness of React because if the state should change then JSX knows to propagate down the component hierarchy to change the view. There are cases were a single state object is not appropriate. An example for a small web app is a form. Forms need there own state object so that validation can occur during user input.
Bryce Williams, a former student, developed a second fishing app tutorial using the React framework. To learn React from Bryce’s tutorial, clone the repository:
https://github.com/2017-SD/fishing-tutorial-2
Look for the tutorial folder for the GrailsReactOfflineAppTutorial.pdf
https://github.com/2017-SD/fishing-tutorial-2/blob/master/tutorial/GrailsReactOfflineAppTutorial.pdf
Bryce’s tutorial also uses Spring Security for authentication and authorization. The User controller includes an addition method, getLogin(), to determine if the user is logged in.
class UserController { def springSecurityService def index() { } // this function checks if the user is logged in def getLogin() { User user = springSecurityService.currentUser if (user != null) { render user.fname } else { render false } } }
Notice how springSecurityService is injected into the controller.
The root of the component hierarchy is App.js:
https://github.com/2017-SD/fishing-tutorial-2/blob/master/src/main/webapp/App.js
The app state object is define in the App constructor:
class App extends Component { constructor() { super(); this.state = { logged_in: false, online: true, showing_loading_modal: false, // modal indicating something is loading loading_message: '', // message to display on the modal showing_nc_modal: false, // new catch modal queue: [], // offline upload queue posted_catches: [], // catches in the db showing_detail_modal: false, // catch detail modal display_catch: {}, // catch info to display } } ... }
The render method defines the the component hierarchy. in the App component, the state props are passed down to sub components.
render() { const { logged_in, showing_nc_modal, online, queue, posted_catches, showing_detail_modal, display_catch, showing_loading_modal, loading_message } = this.state // all items true if not empty let items_in_queue = (!isEmpty(queue)) // for upload queue let catches = (!isEmpty(posted_catches)) // for posted catches let catch_to_display = (display_catch !== {}) // for catch detail modal return ( <div> <AppNav logged_in={logged_in} online={online} /> { /* shows if there is a queue */ items_in_queue && <div> <UploadQueue queue={queue}/> <br/> </div> } { /* only upload if online & logged in */ items_in_queue && online && logged_in && <div style={app_styles.button}> <Button onClick={this.uploadQueue} bsStyle="success">Submit Pending Catches</Button> <br/> </div> } <div style={app_styles.button}> <Button onClick={this.showNewCatchModal} bsStyle="success">New Catch</Button> </div> <br/> { /* only show catches if online & logged in */ online && logged_in && <div style={app_styles.button}> <Button onClick={this.showCatches} bsStyle="success">Show Your Catches</Button> <br/> </div> } { /* only show if there have been catches posted */ catches && <div> <CatchTable catches={posted_catches} showDetailModal={this.showDetailModal} /> </div> } {/* modals */} <LoadingModal showing={showing_loading_modal} hideModal={this.hideLoadingModal} message={loading_message} /> <NewCatchModal showing={showing_nc_modal} hideModal={this.hideNewCatchModal} submitNewCatch={this.submitNewCatch} /> { catch_to_display && <CatchDetailModal showing={showing_detail_modal} hideModal={this.hideDetailModal} selected_catch={display_catch} /> } </div> ); } }
For example the CatchDetailModal is passed the the showing and selected_catch state variables and the hideModal function.
<CatchDetailModal showing={showing_detail_modal} hideModal={this.hideDetailModal} selected_catch={display_catch} />
Note that CatchDetailModal is another React component, in this case a function not a class because it does not have a state.