Bumped the version up to 1.9.3
[moodle.git] / lib / scroll_to_errors.js
blob928f9487ea2335e3dd9f938ea8209aa107e0f79e
1 // keep the global scope clean
2 (function() {
4     var id = null;
5     var warnings = null;
6     var continueBtn = null;
8     var getElementPosY = function(obj) {
9         var y = 0;
10         while (obj.offsetParent) {
11             y += obj.offsetTop;
12             obj = obj.offsetParent;
13         }
14         return y;
15     };
17     // ugh, find scroll position in 3 possible ways
18     var getScrollY = function() {
19         return self.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
20     };
22     var initScroll = function(obj) {
23         // if we scroll to the warning div itself the sql that caused the warning will be off the top of the page 
24         // so we can look through the page for a preceeding div and base the scroll position on that        
25         var prevDiv = findPreviousSibling(obj, 'div');
26         // if the warning div doesn't have a previous sibling div scroll to the top of the page instead
27         var y = (prevDiv) ? getElementPosY(prevDiv) + prevDiv.offsetHeight : 0;
29         if (id) {
30             clearInterval(id);
31         }
33         // scroll with a little bit of easing, I guess it's a matter of personal taste whether it would be 
34         // better to scroll the page directly to the point using window.scrollTo(0, y). But I think easing 
35         // makes it a little clearer to the user that they're scrolling through the page :-)
36         id = setInterval(function() {
37             var ys = getScrollY();
38             // the stuff on arguments.callee is a check to stop scrolling if we've reached the end of the page 
39             // and can't scroll any further
40             if ((arguments.callee.oldPos && arguments.callee.oldPos == ys) || Math.abs(y - ys) < 5) {
41                 arguments.callee.oldPos = null;
42                 window.scrollTo(0, y);
43                 clearInterval(id);
44                 id = null;
45             } else {
46                 window.scrollTo(0, Math.round(ys + ((y - ys) / 2)));
47             }
48             arguments.callee.oldPos = ys;
49         }, 60);
50     };
52     // return nodes with a class name that matches regexp - if individual is set we're only looking 
53     // for a single node
54     var filterNodesByClassName = function(nodes, regexp, individual) {
55         var filtered = [];
56         var n = nodes.length;
57         for (var i = 0; i < n; ++i) {
58             if (nodes[i].className && nodes[i].className.match(regexp)) {
59                 if (individual) {
60                     return nodes[i];
61                 }
62                 filtered.push(nodes[i]);
63             }
64         }
65         return filtered;
66     };
68     // look through the previous siblings of an element and find the first one with a given node name
69     var findPreviousSibling = function(obj, nodeName) {
70         while (obj = obj.previousSibling) {
71             if (obj.nodeName.toLowerCase() == nodeName) {
72                 return obj;
73             }
74         }
75         return false;
76     };
78     // create the links to scroll around the page. warningIndex is used to look up the element in the 
79     // warnings array that should be scrolled to
80     var createWarningSkipLink = function(linkText, warningIndex, style) {
81         var link = document.createElement('a');
82         link.href = 'javascript:;';
83         link.warningIndex = warningIndex;
84         link.appendChild(document.createTextNode(linkText));
85         link.onclick = function() {
86              initScroll(warnings[this.warningIndex]);
87         };
89         if (style) {
90             for (var x in style) {
91                 link.style[x] = style[x];
92             }
93         }
94         return link;
95     };
96    
98     var checkWarnings = function() {
99         // look for div tags with the class name notifyproblem
100         warnings = filterNodesByClassName(document.getElementsByTagName('div'), /(^|\b)notifyproblem(\b|$)/);
101         // and find the continue button
102         continueBtn = filterNodesByClassName(document.getElementsByTagName('div'), /(^|\b)continuebutton(\b|$)/, true);
103                 
104         var n = warnings.length; // find how many warnings
105         warnings[warnings.length] = continueBtn; // then add the continue button to the array
107         var link;
108         var statusOk = false;
109         for (var i = 0; i < n; ++i) {
110             // add a "next" link to all warnings except the last one on the page
111             if (i < n - 1) {
112                 link = createWarningSkipLink('Scroll to next warning', i + 1, {paddingLeft: '1em'});
113             } else { 
114                 // on the last link add a link to go to the continue button
115                 link = createWarningSkipLink('Scroll to continue button', i + 1, {paddingLeft: '1em'});
116             }
117             warnings[i].appendChild(link);
118             // and add a "previous" link to all except the first
119             if (i > 0) {
120                 link = createWarningSkipLink('Scroll to previous warning', i - 1, {paddingRight: '1em'});
121                 warnings[i].insertBefore(link, warnings[i].firstChild);
122             }
123         }
124         
125         
126         var contentDiv = document.getElementById('content');
127         if (contentDiv) {
128             // create a message to display at the top of the page, with a link to the first warning
129             // or to the continue button if there were no warnings on the page
130             var p = document.createElement('p');
131             if (n > 0) {
132                 var warningText = (n == 1) ? 'warning' : 'warnings';
133                 link = createWarningSkipLink('Scroll to the first warning', 0);
134                 p.appendChild(document.createTextNode('This script generated ' + n + ' ' + warningText + ' - '));
135                 p.className = 'notifyproblem';
136             } else {
137                 link = createWarningSkipLink('Scroll to the continue button', 0);
138                 p.appendChild(document.createTextNode('No warnings - '));
139                 p.className = 'notifysuccess';
140                 statusOk = true;
141             }
142             p.appendChild(link);
143             contentDiv.insertBefore(p, contentDiv.firstChild);
144         }
145         
146         // automatically scroll to the first warning or continue button
147         initScroll(warnings[0]);
148         if (statusOk && installautopilot) {//global JS variable
149             document.forms[0].submit();//auto submit
150         }
151     };
153     // load should be a document event, but most browsers use window 
154     if (window.addEventListener) {
155         window.addEventListener('load', checkWarnings, false);
156     } else if (document.addEventListener) {
157         document.addEventListener('load', checkWarnings, false);
158     } else if (window.attachEvent) {
159         // sometimes IE doesn't report scrollTop correctly (it might be a quirk of this specific page, I don't know)
160         // but using scrollTo once to begin makes sure things work
161         window.scrollTo(0, 1);
162         window.attachEvent('onload', checkWarnings);
163     }
165 })();