Merge branch 'MDL-69520-master' of git://github.com/sarjona/moodle
[moodle.git] / admin / tool / usertours / amd / src / usertours.js
1 /**
2  * User tour control library.
3  *
4  * @module     tool_usertours/usertours
5  * @class      usertours
6  * @package    tool_usertours
7  * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
8  */
9 define(
10 ['core/ajax', 'tool_usertours/tour', 'jquery', 'core/templates', 'core/str', 'core/log', 'core/notification'],
11 function(ajax, BootstrapTour, $, templates, str, log, notification) {
12     var usertours = {
13         tourId: null,
15         currentTour: null,
17         /**
18          * Initialise the user tour for the current page.
19          *
20          * @method  init
21          * @param   {Array}    tourDetails      The matching tours for this page.
22          * @param   {Array}    filters          The names of all client side filters.
23          */
24         init: function(tourDetails, filters) {
25             let requirements = [];
26             for (var req = 0; req < filters.length; req++) {
27                 requirements[req] = 'tool_usertours/filter_' + filters[req];
28             }
29             require(requirements, function() {
30                 // Run the client side filters to find the first matching tour.
31                 let matchingTour = null;
32                 for (let key in tourDetails) {
33                     let tour = tourDetails[key];
34                     for (let i = 0; i < filters.length; i++) {
35                         let filter = arguments[i];
36                         if (filter.filterMatches(tour)) {
37                             matchingTour = tour;
38                         } else {
39                             // If any filter doesn't match, move on to the next tour.
40                             matchingTour = null;
41                             break;
42                         }
43                     }
44                     // If all filters matched then use this tour.
45                     if (matchingTour) {
46                         break;
47                     }
48                 }
50                 if (matchingTour === null) {
51                     return;
52                 }
54                 // Only one tour per page is allowed.
55                 usertours.tourId = matchingTour.tourId;
57                 let startTour = matchingTour.startTour;
58                 if (typeof startTour === 'undefined') {
59                     startTour = true;
60                 }
62                 if (startTour) {
63                     // Fetch the tour configuration.
64                     usertours.fetchTour(usertours.tourId);
65                 }
67                 usertours.addResetLink();
68                 // Watch for the reset link.
69                 $('body').on('click', '[data-action="tool_usertours/resetpagetour"]', function(e) {
70                     e.preventDefault();
71                     usertours.resetTourState(usertours.tourId);
72                 });
73             });
74         },
76         /**
77          * Fetch the configuration specified tour, and start the tour when it has been fetched.
78          *
79          * @method  fetchTour
80          * @param   {Number}    tourId      The ID of the tour to start.
81          */
82         fetchTour: function(tourId) {
83             M.util.js_pending('admin_usertour_fetchTour' + tourId);
84             $.when(
85                 ajax.call([
86                     {
87                         methodname: 'tool_usertours_fetch_and_start_tour',
88                         args: {
89                             tourid:     tourId,
90                             context:    M.cfg.contextid,
91                             pageurl:    window.location.href,
92                         }
93                     }
94                 ])[0],
95                 templates.render('tool_usertours/tourstep', {})
96             )
97             .then(function(response, template) {
98                 // If we don't have any tour config (because it doesn't need showing for the current user), return early.
99                 if (!response.hasOwnProperty('tourconfig')) {
100                     return;
101                 }
103                 return usertours.startBootstrapTour(tourId, template[0], response.tourconfig);
104             })
105             .always(function() {
106                 M.util.js_complete('admin_usertour_fetchTour' + tourId);
108                 return;
109             })
110             .fail(notification.exception);
111         },
113         /**
114          * Add a reset link to the page.
115          *
116          * @method  addResetLink
117          */
118         addResetLink: function() {
119             var ele;
120             M.util.js_pending('admin_usertour_addResetLink');
122             // Append the link to the most suitable place on the page
123             // with fallback to legacy selectors and finally the body
124             // if there is no better place.
125             if ($('.tool_usertours-resettourcontainer').length) {
126                 ele = $('.tool_usertours-resettourcontainer');
127             } else if ($('.logininfo').length) {
128                 ele = $('.logininfo');
129             } else if ($('footer').length) {
130                 ele = $('footer');
131             } else {
132                 ele = $('body');
133             }
134             templates.render('tool_usertours/resettour', {})
135             .then(function(html, js) {
136                 templates.appendNodeContents(ele, html, js);
138                 return;
139             })
140             .always(function() {
141                 M.util.js_complete('admin_usertour_addResetLink');
143                 return;
144             })
145             .fail();
146         },
148         /**
149          * Start the specified tour.
150          *
151          * @method  startBootstrapTour
152          * @param   {Number}    tourId      The ID of the tour to start.
153          * @param   {String}    template    The template to use.
154          * @param   {Object}    tourConfig  The tour configuration.
155          * @return  {Object}
156          */
157         startBootstrapTour: function(tourId, template, tourConfig) {
158             if (usertours.currentTour) {
159                 // End the current tour, but disable end tour handler.
160                 tourConfig.onEnd = null;
161                 usertours.currentTour.endTour();
162                 delete usertours.currentTour;
163             }
165             // Normalize for the new library.
166             tourConfig.eventHandlers = {
167                 afterEnd: [usertours.markTourComplete],
168                 afterRender: [usertours.markStepShown],
169             };
171             // Sort out the tour name.
172             tourConfig.tourName = tourConfig.name;
173             delete tourConfig.name;
175             // Add the template to the configuration.
176             // This enables translations of the buttons.
177             tourConfig.template = template;
179             tourConfig.steps = tourConfig.steps.map(function(step) {
180                 if (typeof step.element !== 'undefined') {
181                     step.target = step.element;
182                     delete step.element;
183                 }
185                 if (typeof step.reflex !== 'undefined') {
186                     step.moveOnClick = !!step.reflex;
187                     delete step.reflex;
188                 }
190                 if (typeof step.content !== 'undefined') {
191                     step.body = step.content;
192                     delete step.content;
193                 }
195                 return step;
196             });
198             usertours.currentTour = new BootstrapTour(tourConfig);
199             return usertours.currentTour.startTour();
200         },
202         /**
203          * Mark the specified step as being shownd by the user.
204          *
205          * @method  markStepShown
206          */
207         markStepShown: function() {
208             var stepConfig = this.getStepConfig(this.getCurrentStepNumber());
209             $.when(
210                 ajax.call([
211                     {
212                         methodname: 'tool_usertours_step_shown',
213                         args: {
214                             tourid:     usertours.tourId,
215                             context:    M.cfg.contextid,
216                             pageurl:    window.location.href,
217                             stepid:     stepConfig.stepid,
218                             stepindex:  this.getCurrentStepNumber(),
219                         }
220                     }
221                 ])[0]
222             ).fail(log.error);
223         },
225         /**
226          * Mark the specified tour as being completed by the user.
227          *
228          * @method  markTourComplete
229          */
230         markTourComplete: function() {
231             var stepConfig = this.getStepConfig(this.getCurrentStepNumber());
232             $.when(
233                 ajax.call([
234                     {
235                         methodname: 'tool_usertours_complete_tour',
236                         args: {
237                             tourid:     usertours.tourId,
238                             context:    M.cfg.contextid,
239                             pageurl:    window.location.href,
240                             stepid:     stepConfig.stepid,
241                             stepindex:  this.getCurrentStepNumber(),
242                         }
243                     }
244                 ])[0]
245             ).fail(log.error);
246         },
248         /**
249          * Reset the state, and restart the the tour on the current page.
250          *
251          * @method  resetTourState
252          * @param   {Number}    tourId      The ID of the tour to start.
253          */
254         resetTourState: function(tourId) {
255             $.when(
256                 ajax.call([
257                     {
258                         methodname: 'tool_usertours_reset_tour',
259                         args: {
260                             tourid:     tourId,
261                             context:    M.cfg.contextid,
262                             pageurl:    window.location.href,
263                         }
264                     }
265                 ])[0]
266             ).then(function(response) {
267                 if (response.startTour) {
268                     usertours.fetchTour(response.startTour);
269                 }
270                 return;
271             }).fail(notification.exception);
272         }
273     };
275     return /** @alias module:tool_usertours/usertours */ {
276         /**
277          * Initialise the user tour for the current page.
278          *
279          * @method  init
280          * @param   {Number}    tourId      The ID of the tour to start.
281          * @param   {Bool}      startTour   Attempt to start the tour now.
282          */
283         init: usertours.init,
285         /**
286          * Reset the state, and restart the the tour on the current page.
287          *
288          * @method  resetTourState
289          * @param   {Number}    tourId      The ID of the tour to restart.
290          */
291         resetTourState: usertours.resetTourState
292     };
293 });