MDL-20636 Finish off converting question import.
[moodle.git] / question / qengine.js
CommitLineData
fd214b59
TH
1/**
2 * Scroll manager is a class that help with saving the scroll positing when you
3 * click on an action icon, and then when the page is reloaded after processing
4 * the action, it scrolls you to exactly where you were. This is much nicer for
5 * the user.
6 *
7 * To use this in your code, you need to ensure that:
8 * 1. The button that triggers the action has to have a click event handler that
9 * calls M.core_scroll_manager.save_scroll_pos
10 * 2. The script that process the action has to grab the scrollpos parameter
11 * using $scrollpos = optional_param('scrollpos', 0, PARAM_INT);
12 * 3. After doing the processing, it must add ->param('scrollpos', $scrollpos)
13 * to the URL that it redirects to.
14 * 4. Finally, on the page that is reloaded (which should be the same as the one
15 * the user started on) you need to call M.core_scroll_manager.scroll_to_saved_pos
16 * on page load.
17 */
18M.core_scroll_manager = M.core_scroll_manager || {};
19
20/**
21 * In the form that contains the element, set the value of the form field with
22 * name scrollpos to the current scroll position. If there is no element with
23 * that name, it creates a hidden form field wiht that name within the form.
24 * @param element the element in the form. Should be something that can be
25 * passed to Y.one.
26 */
27M.core_scroll_manager.save_scroll_pos = function(Y, element) {
28 if (typeof(element) == 'string') {
29 // Have to use getElementById here because element id can contain :.
30 element = Y.one(document.getElementById(element));
31 }
32 var form = element.ancestor('form');
33 if (!form) {
34 return;
35 }
36 var scrollpos = form.one('input[name=scrollpos]');
37 if (!scrollpos) {
38 scrollpos = form.appendChild(form.create('<input type="hidden" name="scrollpos" />'));
39 }
40 scrollpos.set('value', form.get('docScrollY'));
41}
42
43/**
44 * Event handler that can be used on a link. Assumes that the link already
45 * contains at least one URL parameter.
46 */
47M.core_scroll_manager.save_scroll_action = function(e) {
48 var link = e.target.ancestor('a[href]');
49 if (!link) {
50 M.core_scroll_manager.save_scroll_pos({}, e.target);
51 return;
52 }
53 link.set('href', link.get('href') + '&scrollpos=' + link.get('docScrollY'));
54}
55
56/**
57 * If there is a parameter like scrollpos=123 in the URL, scroll to that saved position.
58 */
59M.core_scroll_manager.scroll_to_saved_pos = function(Y) {
60 var matches = window.location.href.match(/^.*[?&]scrollpos=(\d*)(?:&|$|#).*$/, '$1');
61 if (matches) {
62 // onDOMReady is the effective one here. I am leaving the immediate call to
63 // window.scrollTo in case it reduces flicker.
64 window.scrollTo(0, matches[1]);
65 Y.on('domready', function() { window.scrollTo(0, matches[1]); });
66
67 // And the following horror is necessary to make it work in IE 8.
68 // Note that the class ie8 on body is only there in Moodle 2.0 and OU Moodle.
69 if (Y.one('body').hasClass('ie')) {
70 M.core_scroll_manager.force_ie_to_scroll(matches[1])
71 }
72 }
73}
74
75/**
76 * Beat IE into submission.
77 * @param targetpos the target scroll position.
78 */
79M.core_scroll_manager.force_ie_to_scroll = function(targetpos) {
80 var hackcount = 25;
81 function do_scroll() {
82 window.scrollTo(0, targetpos);
83 hackcount -= 1;
84 if (hackcount > 0) {
85 setTimeout(do_scroll, 10);
86 }
87 }
88 Y.on('load', do_scroll, window);
89}
90
157434a5
TH
91M.core_question_engine = M.core_question_engine || {};
92
06f8ed54
TH
93/**
94 * Flag used by M.core_question_engine.prevent_repeat_submission.
95 */
96M.core_question_engine.questionformalreadysubmitted = false;
97
98/**
99 * Initialise a question submit button. This saves the scroll position and
100 * sets the fragment on the form submit URL so the page reloads in the right place.
101 * @param id the id of the button in the HTML.
102 * @param slot the number of the question_attempt within the usage.
103 */
0fafed0f
TH
104M.core_question_engine.init_submit_button = function(Y, button, slot) {
105 var buttonel = document.getElementById(button);
06f8ed54 106 Y.on('click', function(e) {
fd214b59 107 M.core_scroll_manager.save_scroll_pos(Y, button);
0fafed0f
TH
108 buttonel.form.action = buttonel.form.action + '#q' + slot;
109 }, buttonel);
06f8ed54
TH
110}
111
157434a5
TH
112/**
113 * Initialise a form that contains questions printed using print_question.
114 * This has the effect of:
115 * 1. Turning off browser autocomlete.
116 * 2. Stopping enter from submitting the form (or toggling the next flag) unless
117 * keyboard focus is on the submit button or the flag.
118 * 3. Removes any '.questionflagsavebutton's, since we have JavaScript to toggle
06f8ed54
TH
119 * the flags using ajax.
120 * 4. Scroll to the position indicated by scrollpos= in the URL, if it is there.
121 * 5. Prevent the user from repeatedly submitting the form.
157434a5
TH
122 * @param Y the Yahoo object. Needs to have the DOM and Event modules loaded.
123 * @param form something that can be passed to Y.one, to find the form element.
124 */
125M.core_question_engine.init_form = function(Y, form) {
126 Y.one(form).setAttribute('autocomplete', 'off');
06f8ed54
TH
127
128 Y.on('submit', M.core_question_engine.prevent_repeat_submission, form, form, Y);
129
157434a5
TH
130 Y.on('key', function (e) {
131 if (!e.target.test('a') && !e.target.test('input[type=submit]') &&
132 !e.target.test('input[type=img]')) {
133 e.preventDefault();
134 }
135 }, form, 'press:13');
06f8ed54 136
157434a5 137 Y.one(form).all('.questionflagsavebutton').remove();
06f8ed54 138
fd214b59 139 M.core_scroll_manager.scroll_to_saved_pos(Y);
06f8ed54
TH
140}
141
142/**
143 * Event handler to stop the quiz form being submitted more than once.
144 * @param e the form submit event.
145 * @param form the form element.
146 */
0fafed0f 147M.core_question_engine.prevent_repeat_submission = function(e, Y) {
06f8ed54
TH
148 if (M.core_question_engine.questionformalreadysubmitted) {
149 e.halt();
150 return;
151 }
152
153 setTimeout(function() {
0fafed0f 154 Y.all('input[type=submit]').set('disabled', true);
06f8ed54
TH
155 }, 0);
156 M.core_question_engine.questionformalreadysubmitted = true;
157}