diff --git a/README.md b/README.md index e0ac2ca..51160d2 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Abraham injects dynamically-generated [Shepherd](https://shepherdjs.dev/) JavaSc * Define tour content with simple YAML files, in any/many languages. * Organize tours by controller and action. +* Trigger tours automatically on page load or manually via JavaScript event. * Plays nicely with Turbolinks. * Ships with two basic CSS themes (default & dark) -- or write your own @@ -77,7 +78,7 @@ Tell Abraham where to insert its generated JavaScript in `app/views/layouts/appl ## Defining your tours -Define your tours in the `config/tours` directory. Its directory structure should mirror your application's controllers, and the tour files should mirror your actions/views. +Define your tours in the `config/tours` directory corresponding to the views defined in your application. Its directory structure should mirror your application's controllers, and the tour files should mirror your actions/views. ``` config/ @@ -92,11 +93,15 @@ config/ └── show.es.yml ``` -NB: You must specify a locale in the filename, even if you're only supporting one language. +For example, per above, when a Spanish-speaking user visits `/articles/`, they'll see the tours defined by `config/tours/articles/index.es.yml`. + +(Note: You must specify a locale in the filename, even if you're only supporting one language.) ### Tour content -A tour is composed of a series of steps. A step may have a title and must have a description. You may attach a step to a particular element on the page, and place the callout in a particular position (see below). +Within a tour file, each tour is composed of a series of **steps**. A step may have a title and must have a description. You may attach a step to a particular element on the page, and place the callout in a particular position. + +In this example, we define a tour called "intro" with 3 steps: ```yaml intro: @@ -129,7 +134,7 @@ When you specify an `attachTo` element, use the `placement` option to choose whe * `bottom left` * `bottom right` * `center` / `middle` / `middle center` -* `left` / `middle left' +* `left` / `middle left` * `right` / `middle right` * `top` / `top center` * `top left` @@ -140,6 +145,38 @@ Abraham tries to be helpful when your tour steps attach to page elements that ar * If your first step is attached to a particular element, and that element is not present on the page, the tour won't start. ([#28](https://github.com/actmd/abraham/issues/28)) * If your tour has an intermediate step attached to a missing element, Abraham will skip that step and automatically show the next. ([#6](https://github.com/actmd/abraham/issues/6)) +### Automatic vs. manual tours + +By default, Abraham will automatically trigger a tour that the current user hasn't seen yet. You can instead define a tour to be triggered manually using the `trigger` option: + +```yml +walkthrough: + trigger: "manual" + steps: + 1: + text: "This walkthrough will show you how to..." +``` + +Abraham creates a JavaScript event based on the tour name that you can wire into a link or button on that page. In the above example, you would use the `abraham:walthrough:startNow` event to make the tour appear: + +``` + + + +``` + +...or if you use jQuery: + +``` + +``` + ### Testing your tours Abraham loads tour definitions once when you start your server. Restart your server to see tour changes. diff --git a/app/assets/javascripts/abraham/index.js b/app/assets/javascripts/abraham/index.js index b93957b..8d6b3a2 100644 --- a/app/assets/javascripts/abraham/index.js +++ b/app/assets/javascripts/abraham/index.js @@ -1,6 +1,11 @@ //= require js-cookie/src/js.cookie //= require shepherd.js/dist/js/shepherd +var abrahamReady = (callback) => { + if (document.readyState != "loading") callback(); + else document.addEventListener("DOMContentLoaded", callback); +} + document.addEventListener('turbolinks:before-cache', function() { // Remove visible product tours document.querySelectorAll(".shepherd-element").forEach(function(el) { el.remove() }); diff --git a/app/helpers/abraham_helper.rb b/app/helpers/abraham_helper.rb index 4072228..99de648 100644 --- a/app/helpers/abraham_helper.rb +++ b/app/helpers/abraham_helper.rb @@ -1,26 +1,57 @@ # frozen_string_literal: true module AbrahamHelper + # def abraham_tour + # # Do we have tours for this controller/action in the user's locale? + # tours = Rails.configuration.abraham.tours["#{controller_name}.#{action_name}.#{I18n.locale}"] + + # tours ||= Rails.configuration.abraham.tours["#{controller_name}.#{action_name}.#{I18n.default_locale}"] + + # if tours + # completed = AbrahamHistory.where( + # creator_id: current_user.id, + # controller_name: controller_name, + # action_name: action_name + # ) + # remaining = tours.keys - completed.map(&:tour_name) + + # if remaining.any? + # # Generate the javascript snippet for the next remaining tour + # render(partial: "application/abraham", + # locals: { tour_name: remaining.first, + # steps: tours[remaining.first]["steps"] }) + # end + # end + # end + def abraham_tour # Do we have tours for this controller/action in the user's locale? tours = Rails.configuration.abraham.tours["#{controller_name}.#{action_name}.#{I18n.locale}"] - + # Otherwise, default to the default locale tours ||= Rails.configuration.abraham.tours["#{controller_name}.#{action_name}.#{I18n.default_locale}"] if tours + # Have any automatic tours been completed already? completed = AbrahamHistory.where( creator_id: current_user.id, controller_name: controller_name, action_name: action_name ) - remaining = tours.keys - completed.map(&:tour_name) - if remaining.any? - # Generate the javascript snippet for the next remaining tour - render(partial: "application/abraham", - locals: { tour_name: remaining.first, - steps: tours[remaining.first]["steps"] }) + tour_keys_completed = completed.map(&:tour_name) + tour_keys = tours.keys + + tour_html = '' + + tour_keys.each do |key| + tour_html += render(partial: "application/abraham", + locals: { tour_name: key, + tour_completed: tour_keys_completed.include?(key), + trigger: tours[key]["trigger"], + steps: tours[key]["steps"] }) end + + tour_html.html_safe end end diff --git a/app/views/application/_abraham.html.erb b/app/views/application/_abraham.html.erb index 1053e69..23c1d43 100644 --- a/app/views/application/_abraham.html.erb +++ b/app/views/application/_abraham.html.erb @@ -1,7 +1,8 @@ diff --git a/test/dummy/app/views/dashboard/home.html.erb b/test/dummy/app/views/dashboard/home.html.erb index 5f9c7ec..517c91c 100644 --- a/test/dummy/app/views/dashboard/home.html.erb +++ b/test/dummy/app/views/dashboard/home.html.erb @@ -5,4 +5,17 @@ a content element to notice + + + + + <%= link_to "Other Page", dashboard_other_url %> \ No newline at end of file diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb index 25b0bea..46abe1f 100644 --- a/test/dummy/app/views/layouts/application.html.erb +++ b/test/dummy/app/views/layouts/application.html.erb @@ -11,6 +11,10 @@ <%= yield %> +
+ +

current_user.id = <%= current_user.id %>

+ <%= abraham_tour %> diff --git a/test/dummy/config/tours/dashboard/home.en.yml b/test/dummy/config/tours/dashboard/home.en.yml index 5e66495..2b690a5 100644 --- a/test/dummy/config/tours/dashboard/home.en.yml +++ b/test/dummy/config/tours/dashboard/home.en.yml @@ -17,3 +17,13 @@ intro: attachTo: element: ".notice-me" placement: "right" +a_manual_tour: + trigger: manual + steps: + 1: + text: "You triggered the manual tour" +another_manual_tour: + trigger: manual + steps: + 1: + text: "You triggered the OTHER manual tour" \ No newline at end of file diff --git a/test/dummy/config/tours/dashboard/other.en.yml b/test/dummy/config/tours/dashboard/other.en.yml index 8b0d7ac..799c65e 100644 --- a/test/dummy/config/tours/dashboard/other.en.yml +++ b/test/dummy/config/tours/dashboard/other.en.yml @@ -7,8 +7,8 @@ tour_one: element: "p" placement: "top" -tour_two: - steps: - 1: - title: "TOUR TWO step one ENGLISH" - text: "we show this on your second visit" +# tour_two: +# steps: +# 1: +# title: "TOUR TWO step one ENGLISH" +# text: "we show this on your second visit" diff --git a/test/dummy/test/system/tours_test.rb b/test/dummy/test/system/tours_test.rb index 8d8de07..66bbfe0 100644 --- a/test/dummy/test/system/tours_test.rb +++ b/test/dummy/test/system/tours_test.rb @@ -17,6 +17,11 @@ class ToursTest < ApplicationSystemTestCase assert_selector ".shepherd-button", text: "Continue" find(".shepherd-button", text: "Continue").click + # Now try to manually trigger another tour + find('#show_manual').click + # Even though we triggered another tour, it should not appear since one is already active + assert_selector ".shepherd-element", count: 1, visible: true + # Tour Step 2 assert_selector ".shepherd-header", text: "ENGLISH This step has a title" assert_selector ".shepherd-text", text: "ENGLISH This intermediate step has some text" @@ -40,6 +45,17 @@ class ToursTest < ApplicationSystemTestCase # Tour should not reappear on reload visit dashboard_home_url refute_selector ".shepherd-element" + + # Now start a manual tour + find('#show_manual').click + assert_selector ".shepherd-element", visible: true + assert_selector ".shepherd-text", text: "You triggered the manual tour" + assert_selector ".shepherd-button", text: "Done" + find(".shepherd-button", text: "Done").click + + # Even though we finished the manual tour, we can start it again right away + find('#show_manual').click + assert_selector ".shepherd-element", visible: true end test "mark a tour for Later and it will not come back in this session" do