Here I'm sharing a couple of quick notes on how I implemented an onboarding tour with mock data in my SaaS app WiseCash, which essentially reduced my customer support time to zero. This is a follow-up of this tweet.
Demo: if you sign-up here (no credit card required), you will see that you can start a quick tour, with fake data, not affecting your actual user data.
The "tour" button redirects to a regular page but with a specific "tour=1" parameter (https://www.wisecashhq.com/insights/burn-chart?tour=1 when logged in)
The top-level controller (ApplicationController
) has a way to determine if we are in tour mode or not, for both regular pages or AJAX calls:
def tour_mode?
(params[:tour] == '1') || (request.headers['X-Tour-Mode'] == '1')
end
This method is used in both controllers and views to figure out if the regular behaviour should occur, or if some "tour" behaviour must be achieved instead.
Later the app relies on that to decide to use real user data or a TourDataProvider
(see code below) fake data (always up to date & relative to "today", which is important to compute dynamically since WiseCash charts are aiming at forecasting the bank account balance):
source = tour_mode? ? TourDataProvider : current_user
The TourDataProvider
is a standalone class building non persisted objects dynamically, adjusted for each step of the tour (because different charts require different data to actually show something interesting).
All "destructive" actions are forbidden in tour mode, with things such as:
<% unless tour_mode? %>
<a class="hoveredit" href="#" data-bind="click: editAccount">edit</a>
<% end %>
But also in the controllers:
before_action :forbid_in_tour_mode, except: :index
private
def forbid_in_tour_mode
return false if tour_mode?
end
On the front-end side, the app instructs the Javascript that we are in tour mode & setup XHR accordingly, then start hopscotch which start manually on each page:
// save this around so we can selectively disable features
window.tourMode = <%= tour_mode?.to_json %>;
<% if tour_mode? %>
Tour.init(<%= Integer(params[:step] || 0) %>);
<% end %>
class @Tour
@init: (step = 0) =>
# pass the tour flag for all ajax requests on our domain
$.ajaxPrefilter (options, originalOptions, xhr) =>
if options.type != 'GET'
alert("Data is read-only during this tour. Come back later!")
xhr.abort()
if !options.crossDomain
xhr.setRequestHeader('X-Tour-Mode', '1')
xhr.setRequestHeader('X-Tour-DataSet', @tourDataSetFor(step))
$(document).ready () =>
hopscotch.startTour(Tour.tour(), step)
That's the gist of it. Feel free to ask questions on the gist, happy to give more insights.