added curriculum section. Needs working.
[theme-danix.xyz.git] / static / js / util.js
CommitLineData
748286b5 1(function($) {\r
2\r
3 /**\r
4 * Generate an indented list of links from a nav. Meant for use with panel().\r
5 * @return {jQuery} jQuery object.\r
6 */\r
7 $.fn.navList = function() {\r
8\r
9 var $this = $(this);\r
10 $a = $this.find('a'),\r
11 b = [];\r
12\r
13 $a.each(function() {\r
14\r
15 var $this = $(this),\r
16 indent = Math.max(0, $this.parents('li').length - 1),\r
17 href = $this.attr('href'),\r
18 target = $this.attr('target');\r
19\r
20 b.push(\r
21 '<a ' +\r
22 'class="link depth-' + indent + '"' +\r
23 ( (typeof target !== 'undefined' && target != '') ? ' target="' + target + '"' : '') +\r
24 ( (typeof href !== 'undefined' && href != '') ? ' href="' + href + '"' : '') +\r
25 '>' +\r
26 '<span class="indent-' + indent + '"></span>' +\r
27 $this.text() +\r
28 '</a>'\r
29 );\r
30\r
31 });\r
32\r
33 return b.join('');\r
34\r
35 };\r
36\r
37 /**\r
38 * Panel-ify an element.\r
39 * @param {object} userConfig User config.\r
40 * @return {jQuery} jQuery object.\r
41 */\r
42 $.fn.panel = function(userConfig) {\r
43\r
44 // No elements?\r
45 if (this.length == 0)\r
46 return $this;\r
47\r
48 // Multiple elements?\r
49 if (this.length > 1) {\r
50\r
51 for (var i=0; i < this.length; i++)\r
52 $(this[i]).panel(userConfig);\r
53\r
54 return $this;\r
55\r
56 }\r
57\r
58 // Vars.\r
59 var $this = $(this),\r
60 $body = $('body'),\r
61 $window = $(window),\r
62 id = $this.attr('id'),\r
63 config;\r
64\r
65 // Config.\r
66 config = $.extend({\r
67\r
68 // Delay.\r
69 delay: 0,\r
70\r
71 // Hide panel on link click.\r
72 hideOnClick: false,\r
73\r
74 // Hide panel on escape keypress.\r
75 hideOnEscape: false,\r
76\r
77 // Hide panel on swipe.\r
78 hideOnSwipe: false,\r
79\r
80 // Reset scroll position on hide.\r
81 resetScroll: false,\r
82\r
83 // Reset forms on hide.\r
84 resetForms: false,\r
85\r
86 // Side of viewport the panel will appear.\r
87 side: null,\r
88\r
89 // Target element for "class".\r
90 target: $this,\r
91\r
92 // Class to toggle.\r
93 visibleClass: 'visible'\r
94\r
95 }, userConfig);\r
96\r
97 // Expand "target" if it's not a jQuery object already.\r
98 if (typeof config.target != 'jQuery')\r
99 config.target = $(config.target);\r
100\r
101 // Panel.\r
102\r
103 // Methods.\r
104 $this._hide = function(event) {\r
105\r
106 // Already hidden? Bail.\r
107 if (!config.target.hasClass(config.visibleClass))\r
108 return;\r
109\r
110 // If an event was provided, cancel it.\r
111 if (event) {\r
112\r
113 event.preventDefault();\r
114 event.stopPropagation();\r
115\r
116 }\r
117\r
118 // Hide.\r
119 config.target.removeClass(config.visibleClass);\r
120\r
121 // Post-hide stuff.\r
122 window.setTimeout(function() {\r
123\r
124 // Reset scroll position.\r
125 if (config.resetScroll)\r
126 $this.scrollTop(0);\r
127\r
128 // Reset forms.\r
129 if (config.resetForms)\r
130 $this.find('form').each(function() {\r
131 this.reset();\r
132 });\r
133\r
134 }, config.delay);\r
135\r
136 };\r
137\r
138 // Vendor fixes.\r
139 $this\r
140 .css('-ms-overflow-style', '-ms-autohiding-scrollbar')\r
141 .css('-webkit-overflow-scrolling', 'touch');\r
142\r
143 // Hide on click.\r
144 if (config.hideOnClick) {\r
145\r
146 $this.find('a')\r
147 .css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');\r
148\r
149 $this\r
150 .on('click', 'a', function(event) {\r
151\r
152 var $a = $(this),\r
153 href = $a.attr('href'),\r
154 target = $a.attr('target');\r
155\r
156 if (!href || href == '#' || href == '' || href == '#' + id)\r
157 return;\r
158\r
159 // Cancel original event.\r
160 event.preventDefault();\r
161 event.stopPropagation();\r
162\r
163 // Hide panel.\r
164 $this._hide();\r
165\r
166 // Redirect to href.\r
167 window.setTimeout(function() {\r
168\r
169 if (target == '_blank')\r
170 window.open(href);\r
171 else\r
172 window.location.href = href;\r
173\r
174 }, config.delay + 10);\r
175\r
176 });\r
177\r
178 }\r
179\r
180 // Event: Touch stuff.\r
181 $this.on('touchstart', function(event) {\r
182\r
183 $this.touchPosX = event.originalEvent.touches[0].pageX;\r
184 $this.touchPosY = event.originalEvent.touches[0].pageY;\r
185\r
186 })\r
187\r
188 $this.on('touchmove', function(event) {\r
189\r
190 if ($this.touchPosX === null\r
191 || $this.touchPosY === null)\r
192 return;\r
193\r
194 var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX,\r
195 diffY = $this.touchPosY - event.originalEvent.touches[0].pageY,\r
196 th = $this.outerHeight(),\r
197 ts = ($this.get(0).scrollHeight - $this.scrollTop());\r
198\r
199 // Hide on swipe?\r
200 if (config.hideOnSwipe) {\r
201\r
202 var result = false,\r
203 boundary = 20,\r
204 delta = 50;\r
205\r
206 switch (config.side) {\r
207\r
208 case 'left':\r
209 result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta);\r
210 break;\r
211\r
212 case 'right':\r
213 result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta));\r
214 break;\r
215\r
216 case 'top':\r
217 result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta);\r
218 break;\r
219\r
220 case 'bottom':\r
221 result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta));\r
222 break;\r
223\r
224 default:\r
225 break;\r
226\r
227 }\r
228\r
229 if (result) {\r
230\r
231 $this.touchPosX = null;\r
232 $this.touchPosY = null;\r
233 $this._hide();\r
234\r
235 return false;\r
236\r
237 }\r
238\r
239 }\r
240\r
241 // Prevent vertical scrolling past the top or bottom.\r
242 if (($this.scrollTop() < 0 && diffY < 0)\r
243 || (ts > (th - 2) && ts < (th + 2) && diffY > 0)) {\r
244\r
245 event.preventDefault();\r
246 event.stopPropagation();\r
247\r
248 }\r
249\r
250 });\r
251\r
252 // Event: Prevent certain events inside the panel from bubbling.\r
253 $this.on('click touchend touchstart touchmove', function(event) {\r
254 event.stopPropagation();\r
255 });\r
256\r
257 // Event: Hide panel if a child anchor tag pointing to its ID is clicked.\r
258 $this.on('click', 'a[href="#' + id + '"]', function(event) {\r
259\r
260 event.preventDefault();\r
261 event.stopPropagation();\r
262\r
263 config.target.removeClass(config.visibleClass);\r
264\r
265 });\r
266\r
267 // Body.\r
268\r
269 // Event: Hide panel on body click/tap.\r
270 $body.on('click touchend', function(event) {\r
271 $this._hide(event);\r
272 });\r
273\r
274 // Event: Toggle.\r
275 $body.on('click', 'a[href="#' + id + '"]', function(event) {\r
276\r
277 event.preventDefault();\r
278 event.stopPropagation();\r
279\r
280 config.target.toggleClass(config.visibleClass);\r
281\r
282 });\r
283\r
284 // Window.\r
285\r
286 // Event: Hide on ESC.\r
287 if (config.hideOnEscape)\r
288 $window.on('keydown', function(event) {\r
289\r
290 if (event.keyCode == 27)\r
291 $this._hide(event);\r
292\r
293 });\r
294\r
295 return $this;\r
296\r
297 };\r
298\r
299 /**\r
300 * Apply "placeholder" attribute polyfill to one or more forms.\r
301 * @return {jQuery} jQuery object.\r
302 */\r
303 $.fn.placeholder = function() {\r
304\r
305 // Browser natively supports placeholders? Bail.\r
306 if (typeof (document.createElement('input')).placeholder != 'undefined')\r
307 return $(this);\r
308\r
309 // No elements?\r
310 if (this.length == 0)\r
311 return $this;\r
312\r
313 // Multiple elements?\r
314 if (this.length > 1) {\r
315\r
316 for (var i=0; i < this.length; i++)\r
317 $(this[i]).placeholder();\r
318\r
319 return $this;\r
320\r
321 }\r
322\r
323 // Vars.\r
324 var $this = $(this);\r
325\r
326 // Text, TextArea.\r
327 $this.find('input[type=text],textarea')\r
328 .each(function() {\r
329\r
330 var i = $(this);\r
331\r
332 if (i.val() == ''\r
333 || i.val() == i.attr('placeholder'))\r
334 i\r
335 .addClass('polyfill-placeholder')\r
336 .val(i.attr('placeholder'));\r
337\r
338 })\r
339 .on('blur', function() {\r
340\r
341 var i = $(this);\r
342\r
343 if (i.attr('name').match(/-polyfill-field$/))\r
344 return;\r
345\r
346 if (i.val() == '')\r
347 i\r
348 .addClass('polyfill-placeholder')\r
349 .val(i.attr('placeholder'));\r
350\r
351 })\r
352 .on('focus', function() {\r
353\r
354 var i = $(this);\r
355\r
356 if (i.attr('name').match(/-polyfill-field$/))\r
357 return;\r
358\r
359 if (i.val() == i.attr('placeholder'))\r
360 i\r
361 .removeClass('polyfill-placeholder')\r
362 .val('');\r
363\r
364 });\r
365\r
366 // Password.\r
367 $this.find('input[type=password]')\r
368 .each(function() {\r
369\r
370 var i = $(this);\r
371 var x = $(\r
372 $('<div>')\r
373 .append(i.clone())\r
374 .remove()\r
375 .html()\r
376 .replace(/type="password"/i, 'type="text"')\r
377 .replace(/type=password/i, 'type=text')\r
378 );\r
379\r
380 if (i.attr('id') != '')\r
381 x.attr('id', i.attr('id') + '-polyfill-field');\r
382\r
383 if (i.attr('name') != '')\r
384 x.attr('name', i.attr('name') + '-polyfill-field');\r
385\r
386 x.addClass('polyfill-placeholder')\r
387 .val(x.attr('placeholder')).insertAfter(i);\r
388\r
389 if (i.val() == '')\r
390 i.hide();\r
391 else\r
392 x.hide();\r
393\r
394 i\r
395 .on('blur', function(event) {\r
396\r
397 event.preventDefault();\r
398\r
399 var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');\r
400\r
401 if (i.val() == '') {\r
402\r
403 i.hide();\r
404 x.show();\r
405\r
406 }\r
407\r
408 });\r
409\r
410 x\r
411 .on('focus', function(event) {\r
412\r
413 event.preventDefault();\r
414\r
415 var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']');\r
416\r
417 x.hide();\r
418\r
419 i\r
420 .show()\r
421 .focus();\r
422\r
423 })\r
424 .on('keypress', function(event) {\r
425\r
426 event.preventDefault();\r
427 x.val('');\r
428\r
429 });\r
430\r
431 });\r
432\r
433 // Events.\r
434 $this\r
435 .on('submit', function() {\r
436\r
437 $this.find('input[type=text],input[type=password],textarea')\r
438 .each(function(event) {\r
439\r
440 var i = $(this);\r
441\r
442 if (i.attr('name').match(/-polyfill-field$/))\r
443 i.attr('name', '');\r
444\r
445 if (i.val() == i.attr('placeholder')) {\r
446\r
447 i.removeClass('polyfill-placeholder');\r
448 i.val('');\r
449\r
450 }\r
451\r
452 });\r
453\r
454 })\r
455 .on('reset', function(event) {\r
456\r
457 event.preventDefault();\r
458\r
459 $this.find('select')\r
460 .val($('option:first').val());\r
461\r
462 $this.find('input,textarea')\r
463 .each(function() {\r
464\r
465 var i = $(this),\r
466 x;\r
467\r
468 i.removeClass('polyfill-placeholder');\r
469\r
470 switch (this.type) {\r
471\r
472 case 'submit':\r
473 case 'reset':\r
474 break;\r
475\r
476 case 'password':\r
477 i.val(i.attr('defaultValue'));\r
478\r
479 x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');\r
480\r
481 if (i.val() == '') {\r
482 i.hide();\r
483 x.show();\r
484 }\r
485 else {\r
486 i.show();\r
487 x.hide();\r
488 }\r
489\r
490 break;\r
491\r
492 case 'checkbox':\r
493 case 'radio':\r
494 i.attr('checked', i.attr('defaultValue'));\r
495 break;\r
496\r
497 case 'text':\r
498 case 'textarea':\r
499 i.val(i.attr('defaultValue'));\r
500\r
501 if (i.val() == '') {\r
502 i.addClass('polyfill-placeholder');\r
503 i.val(i.attr('placeholder'));\r
504 }\r
505\r
506 break;\r
507\r
508 default:\r
509 i.val(i.attr('defaultValue'));\r
510 break;\r
511\r
512 }\r
513 });\r
514\r
515 });\r
516\r
517 return $this;\r
518\r
519 };\r
520\r
521 /**\r
522 * Moves elements to/from the first positions of their respective parents.\r
523 * @param {jQuery} $elements Elements (or selector) to move.\r
524 * @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations.\r
525 */\r
526 $.prioritize = function($elements, condition) {\r
527\r
528 var key = '__prioritize';\r
529\r
530 // Expand $elements if it's not already a jQuery object.\r
531 if (typeof $elements != 'jQuery')\r
532 $elements = $($elements);\r
533\r
534 // Step through elements.\r
535 $elements.each(function() {\r
536\r
537 var $e = $(this), $p,\r
538 $parent = $e.parent();\r
539\r
540 // No parent? Bail.\r
541 if ($parent.length == 0)\r
542 return;\r
543\r
544 // Not moved? Move it.\r
545 if (!$e.data(key)) {\r
546\r
547 // Condition is false? Bail.\r
548 if (!condition)\r
549 return;\r
550\r
551 // Get placeholder (which will serve as our point of reference for when this element needs to move back).\r
552 $p = $e.prev();\r
553\r
554 // Couldn't find anything? Means this element's already at the top, so bail.\r
555 if ($p.length == 0)\r
556 return;\r
557\r
558 // Move element to top of parent.\r
559 $e.prependTo($parent);\r
560\r
561 // Mark element as moved.\r
562 $e.data(key, $p);\r
563\r
564 }\r
565\r
566 // Moved already?\r
567 else {\r
568\r
569 // Condition is true? Bail.\r
570 if (condition)\r
571 return;\r
572\r
573 $p = $e.data(key);\r
574\r
575 // Move element back to its original location (using our placeholder).\r
576 $e.insertAfter($p);\r
577\r
578 // Unmark element as moved.\r
579 $e.removeData(key);\r
580\r
581 }\r
582\r
583 });\r
584\r
585 };\r
586\r
587})(jQuery);