Commit | Line | Data |
---|---|---|
aa9bdbe3 TH |
1 | // This file is part of Moodle - http://moodle.org/ |
2 | // | |
3 | // Moodle is free software: you can redistribute it and/or modify | |
4 | // it under the terms of the GNU General Public License as published by | |
5 | // the Free Software Foundation, either version 3 of the License, or | |
6 | // (at your option) any later version. | |
7 | // | |
8 | // Moodle is distributed in the hope that it will be useful, | |
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | // GNU General Public License for more details. | |
12 | // | |
13 | // You should have received a copy of the GNU General Public License | |
14 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
15 | ||
16 | /** | |
17 | * JavaScript required by the question engine. | |
18 | * | |
19 | * @package moodlecore | |
20 | * @subpackage questionengine | |
21 | * @copyright 2008 The Open University | |
22 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
23 | */ | |
24 | ||
25 | ||
fd214b59 TH |
26 | /** |
27 | * Scroll manager is a class that help with saving the scroll positing when you | |
28 | * click on an action icon, and then when the page is reloaded after processing | |
29 | * the action, it scrolls you to exactly where you were. This is much nicer for | |
30 | * the user. | |
31 | * | |
32 | * To use this in your code, you need to ensure that: | |
33 | * 1. The button that triggers the action has to have a click event handler that | |
34 | * calls M.core_scroll_manager.save_scroll_pos | |
35 | * 2. The script that process the action has to grab the scrollpos parameter | |
36 | * using $scrollpos = optional_param('scrollpos', 0, PARAM_INT); | |
37 | * 3. After doing the processing, it must add ->param('scrollpos', $scrollpos) | |
38 | * to the URL that it redirects to. | |
39 | * 4. Finally, on the page that is reloaded (which should be the same as the one | |
40 | * the user started on) you need to call M.core_scroll_manager.scroll_to_saved_pos | |
41 | * on page load. | |
42 | */ | |
43 | M.core_scroll_manager = M.core_scroll_manager || {}; | |
44 | ||
45 | /** | |
46 | * In the form that contains the element, set the value of the form field with | |
47 | * name scrollpos to the current scroll position. If there is no element with | |
48 | * that name, it creates a hidden form field wiht that name within the form. | |
49 | * @param element the element in the form. Should be something that can be | |
50 | * passed to Y.one. | |
51 | */ | |
52 | M.core_scroll_manager.save_scroll_pos = function(Y, element) { | |
53 | if (typeof(element) == 'string') { | |
54 | // Have to use getElementById here because element id can contain :. | |
55 | element = Y.one(document.getElementById(element)); | |
56 | } | |
57 | var form = element.ancestor('form'); | |
58 | if (!form) { | |
59 | return; | |
60 | } | |
61 | var scrollpos = form.one('input[name=scrollpos]'); | |
62 | if (!scrollpos) { | |
63 | scrollpos = form.appendChild(form.create('<input type="hidden" name="scrollpos" />')); | |
64 | } | |
65 | scrollpos.set('value', form.get('docScrollY')); | |
66 | } | |
67 | ||
68 | /** | |
69 | * Event handler that can be used on a link. Assumes that the link already | |
70 | * contains at least one URL parameter. | |
71 | */ | |
72 | M.core_scroll_manager.save_scroll_action = function(e) { | |
73 | var link = e.target.ancestor('a[href]'); | |
74 | if (!link) { | |
75 | M.core_scroll_manager.save_scroll_pos({}, e.target); | |
76 | return; | |
77 | } | |
78 | link.set('href', link.get('href') + '&scrollpos=' + link.get('docScrollY')); | |
79 | } | |
80 | ||
81 | /** | |
82 | * If there is a parameter like scrollpos=123 in the URL, scroll to that saved position. | |
83 | */ | |
84 | M.core_scroll_manager.scroll_to_saved_pos = function(Y) { | |
85 | var matches = window.location.href.match(/^.*[?&]scrollpos=(\d*)(?:&|$|#).*$/, '$1'); | |
86 | if (matches) { | |
87 | // onDOMReady is the effective one here. I am leaving the immediate call to | |
88 | // window.scrollTo in case it reduces flicker. | |
89 | window.scrollTo(0, matches[1]); | |
90 | Y.on('domready', function() { window.scrollTo(0, matches[1]); }); | |
91 | ||
92 | // And the following horror is necessary to make it work in IE 8. | |
93 | // Note that the class ie8 on body is only there in Moodle 2.0 and OU Moodle. | |
94 | if (Y.one('body').hasClass('ie')) { | |
a40d411e | 95 | M.core_scroll_manager.force_ie_to_scroll(Y, matches[1]) |
fd214b59 TH |
96 | } |
97 | } | |
98 | } | |
99 | ||
100 | /** | |
101 | * Beat IE into submission. | |
102 | * @param targetpos the target scroll position. | |
103 | */ | |
a40d411e | 104 | M.core_scroll_manager.force_ie_to_scroll = function(Y, targetpos) { |
fd214b59 TH |
105 | var hackcount = 25; |
106 | function do_scroll() { | |
107 | window.scrollTo(0, targetpos); | |
108 | hackcount -= 1; | |
109 | if (hackcount > 0) { | |
110 | setTimeout(do_scroll, 10); | |
111 | } | |
112 | } | |
113 | Y.on('load', do_scroll, window); | |
114 | } | |
115 | ||
157434a5 TH |
116 | M.core_question_engine = M.core_question_engine || {}; |
117 | ||
06f8ed54 TH |
118 | /** |
119 | * Flag used by M.core_question_engine.prevent_repeat_submission. | |
120 | */ | |
121 | M.core_question_engine.questionformalreadysubmitted = false; | |
122 | ||
123 | /** | |
124 | * Initialise a question submit button. This saves the scroll position and | |
125 | * sets the fragment on the form submit URL so the page reloads in the right place. | |
579da44a | 126 | * @param button the id of the button in the HTML. |
06f8ed54 | 127 | */ |
579da44a HN |
128 | M.core_question_engine.init_submit_button = function(Y, button) { |
129 | var totalQuestionsInPage = document.querySelectorAll('div.que').length; | |
0fafed0f | 130 | var buttonel = document.getElementById(button); |
579da44a | 131 | var outeruniqueid = buttonel.closest('.que').id; |
06f8ed54 | 132 | Y.on('click', function(e) { |
fd214b59 | 133 | M.core_scroll_manager.save_scroll_pos(Y, button); |
579da44a HN |
134 | if (totalQuestionsInPage > 1) { |
135 | // Only change the form action if the page have more than one question. | |
136 | buttonel.form.action = buttonel.form.action + '#' + outeruniqueid; | |
137 | } | |
0fafed0f | 138 | }, buttonel); |
06f8ed54 TH |
139 | } |
140 | ||
157434a5 TH |
141 | /** |
142 | * Initialise a form that contains questions printed using print_question. | |
143 | * This has the effect of: | |
144 | * 1. Turning off browser autocomlete. | |
145 | * 2. Stopping enter from submitting the form (or toggling the next flag) unless | |
146 | * keyboard focus is on the submit button or the flag. | |
147 | * 3. Removes any '.questionflagsavebutton's, since we have JavaScript to toggle | |
06f8ed54 TH |
148 | * the flags using ajax. |
149 | * 4. Scroll to the position indicated by scrollpos= in the URL, if it is there. | |
150 | * 5. Prevent the user from repeatedly submitting the form. | |
157434a5 TH |
151 | * @param Y the Yahoo object. Needs to have the DOM and Event modules loaded. |
152 | * @param form something that can be passed to Y.one, to find the form element. | |
153 | */ | |
154 | M.core_question_engine.init_form = function(Y, form) { | |
155 | Y.one(form).setAttribute('autocomplete', 'off'); | |
06f8ed54 TH |
156 | |
157 | Y.on('submit', M.core_question_engine.prevent_repeat_submission, form, form, Y); | |
158 | ||
157434a5 TH |
159 | Y.on('key', function (e) { |
160 | if (!e.target.test('a') && !e.target.test('input[type=submit]') && | |
84f74ccf | 161 | !e.target.test('input[type=img]') && !e.target.test('textarea') && !e.target.test('[contenteditable=true]')) { |
157434a5 TH |
162 | e.preventDefault(); |
163 | } | |
164 | }, form, 'press:13'); | |
06f8ed54 | 165 | |
157434a5 | 166 | Y.one(form).all('.questionflagsavebutton').remove(); |
06f8ed54 | 167 | |
fd214b59 | 168 | M.core_scroll_manager.scroll_to_saved_pos(Y); |
06f8ed54 TH |
169 | } |
170 | ||
171 | /** | |
5e8a85aa | 172 | * Event handler to stop a question form being submitted more than once. |
06f8ed54 TH |
173 | * @param e the form submit event. |
174 | * @param form the form element. | |
175 | */ | |
0fafed0f | 176 | M.core_question_engine.prevent_repeat_submission = function(e, Y) { |
06f8ed54 TH |
177 | if (M.core_question_engine.questionformalreadysubmitted) { |
178 | e.halt(); | |
179 | return; | |
180 | } | |
181 | ||
182 | setTimeout(function() { | |
0fafed0f | 183 | Y.all('input[type=submit]').set('disabled', true); |
06f8ed54 TH |
184 | }, 0); |
185 | M.core_question_engine.questionformalreadysubmitted = true; | |
186 | } |