slides.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. /*
  2. Google HTML5 slides template
  3. Authors: Luke Mahé (code)
  4. Marcin Wichary (code and design)
  5. Dominic Mazzoni (browser compatibility)
  6. Charles Chen (ChromeVox support)
  7. URL: http://code.google.com/p/html5slides/
  8. */
  9. var PERMANENT_URL_PREFIX = 'inst/frameworks/html5slides';
  10. var SLIDE_CLASSES = ['far-past', 'past', 'current', 'next', 'far-next'];
  11. var PM_TOUCH_SENSITIVITY = 15;
  12. var curSlide;
  13. /* ---------------------------------------------------------------------- */
  14. /* classList polyfill by Eli Grey
  15. * (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */
  16. if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) {
  17. (function (view) {
  18. var
  19. classListProp = "classList"
  20. , protoProp = "prototype"
  21. , elemCtrProto = (view.HTMLElement || view.Element)[protoProp]
  22. , objCtr = Object
  23. strTrim = String[protoProp].trim || function () {
  24. return this.replace(/^\s+|\s+$/g, "");
  25. }
  26. , arrIndexOf = Array[protoProp].indexOf || function (item) {
  27. for (var i = 0, len = this.length; i < len; i++) {
  28. if (i in this && this[i] === item) {
  29. return i;
  30. }
  31. }
  32. return -1;
  33. }
  34. // Vendors: please allow content code to instantiate DOMExceptions
  35. , DOMEx = function (type, message) {
  36. this.name = type;
  37. this.code = DOMException[type];
  38. this.message = message;
  39. }
  40. , checkTokenAndGetIndex = function (classList, token) {
  41. if (token === "") {
  42. throw new DOMEx(
  43. "SYNTAX_ERR"
  44. , "An invalid or illegal string was specified"
  45. );
  46. }
  47. if (/\s/.test(token)) {
  48. throw new DOMEx(
  49. "INVALID_CHARACTER_ERR"
  50. , "String contains an invalid character"
  51. );
  52. }
  53. return arrIndexOf.call(classList, token);
  54. }
  55. , ClassList = function (elem) {
  56. var
  57. trimmedClasses = strTrim.call(elem.className)
  58. , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
  59. ;
  60. for (var i = 0, len = classes.length; i < len; i++) {
  61. this.push(classes[i]);
  62. }
  63. this._updateClassName = function () {
  64. elem.className = this.toString();
  65. };
  66. }
  67. , classListProto = ClassList[protoProp] = []
  68. , classListGetter = function () {
  69. return new ClassList(this);
  70. }
  71. ;
  72. // Most DOMException implementations don't allow calling DOMException's toString()
  73. // on non-DOMExceptions. Error's toString() is sufficient here.
  74. DOMEx[protoProp] = Error[protoProp];
  75. classListProto.item = function (i) {
  76. return this[i] || null;
  77. };
  78. classListProto.contains = function (token) {
  79. token += "";
  80. return checkTokenAndGetIndex(this, token) !== -1;
  81. };
  82. classListProto.add = function (token) {
  83. token += "";
  84. if (checkTokenAndGetIndex(this, token) === -1) {
  85. this.push(token);
  86. this._updateClassName();
  87. }
  88. };
  89. classListProto.remove = function (token) {
  90. token += "";
  91. var index = checkTokenAndGetIndex(this, token);
  92. if (index !== -1) {
  93. this.splice(index, 1);
  94. this._updateClassName();
  95. }
  96. };
  97. classListProto.toggle = function (token) {
  98. token += "";
  99. if (checkTokenAndGetIndex(this, token) === -1) {
  100. this.add(token);
  101. } else {
  102. this.remove(token);
  103. }
  104. };
  105. classListProto.toString = function () {
  106. return this.join(" ");
  107. };
  108. if (objCtr.defineProperty) {
  109. var classListPropDesc = {
  110. get: classListGetter
  111. , enumerable: true
  112. , configurable: true
  113. };
  114. try {
  115. objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
  116. } catch (ex) { // IE 8 doesn't support enumerable:true
  117. if (ex.number === -0x7FF5EC54) {
  118. classListPropDesc.enumerable = false;
  119. objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
  120. }
  121. }
  122. } else if (objCtr[protoProp].__defineGetter__) {
  123. elemCtrProto.__defineGetter__(classListProp, classListGetter);
  124. }
  125. }(self));
  126. }
  127. /* ---------------------------------------------------------------------- */
  128. /* Slide movement */
  129. function getSlideEl(no) {
  130. if ((no < 0) || (no >= slideEls.length)) {
  131. return null;
  132. } else {
  133. return slideEls[no];
  134. }
  135. };
  136. function updateSlideClass(slideNo, className) {
  137. var el = getSlideEl(slideNo);
  138. if (!el) {
  139. return;
  140. }
  141. if (className) {
  142. el.classList.add(className);
  143. }
  144. for (var i in SLIDE_CLASSES) {
  145. if (className != SLIDE_CLASSES[i]) {
  146. el.classList.remove(SLIDE_CLASSES[i]);
  147. }
  148. }
  149. };
  150. function updateSlides() {
  151. for (var i = 0; i < slideEls.length; i++) {
  152. switch (i) {
  153. case curSlide - 2:
  154. updateSlideClass(i, 'far-past');
  155. break;
  156. case curSlide - 1:
  157. updateSlideClass(i, 'past');
  158. break;
  159. case curSlide:
  160. updateSlideClass(i, 'current');
  161. break;
  162. case curSlide + 1:
  163. updateSlideClass(i, 'next');
  164. break;
  165. case curSlide + 2:
  166. updateSlideClass(i, 'far-next');
  167. break;
  168. default:
  169. updateSlideClass(i);
  170. break;
  171. }
  172. }
  173. triggerLeaveEvent(curSlide - 1);
  174. triggerEnterEvent(curSlide);
  175. window.setTimeout(function() {
  176. // Hide after the slide
  177. disableSlideFrames(curSlide - 2);
  178. }, 301);
  179. enableSlideFrames(curSlide - 1);
  180. enableSlideFrames(curSlide + 2);
  181. if (isChromeVoxActive()) {
  182. speakAndSyncToNode(slideEls[curSlide]);
  183. }
  184. updateHash();
  185. };
  186. function buildNextItem() {
  187. var toBuild = slideEls[curSlide].querySelectorAll('.to-build');
  188. if (!toBuild.length) {
  189. return false;
  190. }
  191. toBuild[0].classList.remove('to-build', '');
  192. if (isChromeVoxActive()) {
  193. speakAndSyncToNode(toBuild[0]);
  194. }
  195. return true;
  196. };
  197. function prevSlide() {
  198. if (curSlide > 0) {
  199. curSlide--;
  200. updateSlides();
  201. }
  202. };
  203. function nextSlide() {
  204. if (buildNextItem()) {
  205. return;
  206. }
  207. if (curSlide < slideEls.length - 1) {
  208. curSlide++;
  209. updateSlides();
  210. }
  211. };
  212. /* Slide events */
  213. function triggerEnterEvent(no) {
  214. var el = getSlideEl(no);
  215. if (!el) {
  216. return;
  217. }
  218. var onEnter = el.getAttribute('onslideenter');
  219. if (onEnter) {
  220. new Function(onEnter).call(el);
  221. }
  222. var evt = document.createEvent('Event');
  223. evt.initEvent('slideenter', true, true);
  224. evt.slideNumber = no + 1; // Make it readable
  225. el.dispatchEvent(evt);
  226. };
  227. function triggerLeaveEvent(no) {
  228. var el = getSlideEl(no);
  229. if (!el) {
  230. return;
  231. }
  232. var onLeave = el.getAttribute('onslideleave');
  233. if (onLeave) {
  234. new Function(onLeave).call(el);
  235. }
  236. var evt = document.createEvent('Event');
  237. evt.initEvent('slideleave', true, true);
  238. evt.slideNumber = no + 1; // Make it readable
  239. el.dispatchEvent(evt);
  240. };
  241. /* Touch events */
  242. function handleTouchStart(event) {
  243. if (event.touches.length == 1) {
  244. touchDX = 0;
  245. touchDY = 0;
  246. touchStartX = event.touches[0].pageX;
  247. touchStartY = event.touches[0].pageY;
  248. document.body.addEventListener('touchmove', handleTouchMove, true);
  249. document.body.addEventListener('touchend', handleTouchEnd, true);
  250. }
  251. };
  252. function handleTouchMove(event) {
  253. if (event.touches.length > 1) {
  254. cancelTouch();
  255. } else {
  256. touchDX = event.touches[0].pageX - touchStartX;
  257. touchDY = event.touches[0].pageY - touchStartY;
  258. }
  259. };
  260. function handleTouchEnd(event) {
  261. var dx = Math.abs(touchDX);
  262. var dy = Math.abs(touchDY);
  263. if ((dx > PM_TOUCH_SENSITIVITY) && (dy < (dx * 2 / 3))) {
  264. if (touchDX > 0) {
  265. prevSlide();
  266. } else {
  267. nextSlide();
  268. }
  269. }
  270. cancelTouch();
  271. };
  272. function cancelTouch() {
  273. document.body.removeEventListener('touchmove', handleTouchMove, true);
  274. document.body.removeEventListener('touchend', handleTouchEnd, true);
  275. };
  276. /* Preloading frames */
  277. function disableSlideFrames(no) {
  278. var el = getSlideEl(no);
  279. if (!el) {
  280. return;
  281. }
  282. var frames = el.getElementsByTagName('iframe');
  283. for (var i = 0, frame; frame = frames[i]; i++) {
  284. disableFrame(frame);
  285. }
  286. };
  287. function enableSlideFrames(no) {
  288. var el = getSlideEl(no);
  289. if (!el) {
  290. return;
  291. }
  292. var frames = el.getElementsByTagName('iframe');
  293. for (var i = 0, frame; frame = frames[i]; i++) {
  294. enableFrame(frame);
  295. }
  296. };
  297. function disableFrame(frame) {
  298. frame.src = 'about:blank';
  299. };
  300. function enableFrame(frame) {
  301. var src = frame._src;
  302. if (frame.src != src && src != 'about:blank') {
  303. frame.src = src;
  304. }
  305. };
  306. function setupFrames() {
  307. var frames = document.querySelectorAll('iframe');
  308. for (var i = 0, frame; frame = frames[i]; i++) {
  309. frame._src = frame.src;
  310. disableFrame(frame);
  311. }
  312. enableSlideFrames(curSlide);
  313. enableSlideFrames(curSlide + 1);
  314. enableSlideFrames(curSlide + 2);
  315. };
  316. function setupInteraction() {
  317. /* Clicking and tapping */
  318. var el = document.createElement('div');
  319. el.className = 'slide-area';
  320. el.id = 'prev-slide-area';
  321. el.addEventListener('click', prevSlide, false);
  322. document.querySelector('section.slides').appendChild(el);
  323. var el = document.createElement('div');
  324. el.className = 'slide-area';
  325. el.id = 'next-slide-area';
  326. el.addEventListener('click', nextSlide, false);
  327. document.querySelector('section.slides').appendChild(el);
  328. /* Swiping */
  329. document.body.addEventListener('touchstart', handleTouchStart, false);
  330. }
  331. /* ChromeVox support */
  332. function isChromeVoxActive() {
  333. if (typeof(cvox) == 'undefined') {
  334. return false;
  335. } else {
  336. return true;
  337. }
  338. };
  339. function speakAndSyncToNode(node) {
  340. if (!isChromeVoxActive()) {
  341. return;
  342. }
  343. cvox.ChromeVox.navigationManager.switchToStrategy(
  344. cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM, 0, true);
  345. cvox.ChromeVox.navigationManager.syncToNode(node);
  346. cvox.ChromeVoxUserCommands.finishNavCommand('');
  347. var target = node;
  348. while (target.firstChild) {
  349. target = target.firstChild;
  350. }
  351. cvox.ChromeVox.navigationManager.syncToNode(target);
  352. };
  353. function speakNextItem() {
  354. if (!isChromeVoxActive()) {
  355. return;
  356. }
  357. cvox.ChromeVox.navigationManager.switchToStrategy(
  358. cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM, 0, true);
  359. cvox.ChromeVox.navigationManager.next(true);
  360. if (!cvox.DomUtil.isDescendantOfNode(
  361. cvox.ChromeVox.navigationManager.getCurrentNode(), slideEls[curSlide])){
  362. var target = slideEls[curSlide];
  363. while (target.firstChild) {
  364. target = target.firstChild;
  365. }
  366. cvox.ChromeVox.navigationManager.syncToNode(target);
  367. cvox.ChromeVox.navigationManager.next(true);
  368. }
  369. cvox.ChromeVoxUserCommands.finishNavCommand('');
  370. };
  371. function speakPrevItem() {
  372. if (!isChromeVoxActive()) {
  373. return;
  374. }
  375. cvox.ChromeVox.navigationManager.switchToStrategy(
  376. cvox.ChromeVoxNavigationManager.STRATEGIES.LINEARDOM, 0, true);
  377. cvox.ChromeVox.navigationManager.previous(true);
  378. if (!cvox.DomUtil.isDescendantOfNode(
  379. cvox.ChromeVox.navigationManager.getCurrentNode(), slideEls[curSlide])){
  380. var target = slideEls[curSlide];
  381. while (target.lastChild){
  382. target = target.lastChild;
  383. }
  384. cvox.ChromeVox.navigationManager.syncToNode(target);
  385. cvox.ChromeVox.navigationManager.previous(true);
  386. }
  387. cvox.ChromeVoxUserCommands.finishNavCommand('');
  388. };
  389. /* Hash functions */
  390. function getCurSlideFromHash() {
  391. var slideNo = parseInt(location.hash.substr(1));
  392. if (slideNo) {
  393. curSlide = slideNo - 1;
  394. } else {
  395. curSlide = 0;
  396. }
  397. };
  398. function updateHash() {
  399. location.replace('#' + (curSlide + 1));
  400. };
  401. /* Event listeners */
  402. function handleBodyKeyDown(event) {
  403. switch (event.keyCode) {
  404. case 39: // right arrow
  405. case 13: // Enter
  406. case 32: // space
  407. case 34: // PgDn
  408. nextSlide();
  409. event.preventDefault();
  410. break;
  411. case 37: // left arrow
  412. case 8: // Backspace
  413. case 33: // PgUp
  414. prevSlide();
  415. event.preventDefault();
  416. break;
  417. case 40: // down arrow
  418. if (isChromeVoxActive()) {
  419. speakNextItem();
  420. } else {
  421. nextSlide();
  422. }
  423. event.preventDefault();
  424. break;
  425. case 38: // up arrow
  426. if (isChromeVoxActive()) {
  427. speakPrevItem();
  428. } else {
  429. prevSlide();
  430. }
  431. event.preventDefault();
  432. break;
  433. }
  434. };
  435. function addEventListeners() {
  436. document.addEventListener('keydown', handleBodyKeyDown, false);
  437. };
  438. /* Initialization */
  439. /* Disable Google Prettify */
  440. // function addPrettify() {
  441. // var els = document.querySelectorAll('pre');
  442. // for (var i = 0, el; el = els[i]; i++) {
  443. // if (!el.classList.contains('noprettyprint')) {
  444. // el.classList.add('prettyprint');
  445. // }
  446. // }
  447. //
  448. // var el = document.createElement('script');
  449. // el.type = 'text/javascript';
  450. // el.src = PERMANENT_URL_PREFIX + 'prettify.js';
  451. // el.onload = function() {
  452. // prettyPrint();
  453. // }
  454. // document.body.appendChild(el);
  455. // };
  456. function addFontStyle() {
  457. var el = document.createElement('link');
  458. el.rel = 'stylesheet';
  459. el.type = 'text/css';
  460. el.href = 'http://fonts.googleapis.com/css?family=' +
  461. 'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono';
  462. document.body.appendChild(el);
  463. };
  464. function addGeneralStyle() {
  465. var el = document.createElement('link');
  466. el.rel = 'stylesheet';
  467. el.type = 'text/css';
  468. el.href = PERMANENT_URL_PREFIX + 'styles.css';
  469. document.body.appendChild(el);
  470. var el = document.createElement('meta');
  471. el.name = 'viewport';
  472. el.content = 'width=1100,height=750';
  473. document.querySelector('head').appendChild(el);
  474. var el = document.createElement('meta');
  475. el.name = 'apple-mobile-web-app-capable';
  476. el.content = 'yes';
  477. document.querySelector('head').appendChild(el);
  478. };
  479. function makeBuildLists() {
  480. for (var i = curSlide, slide; slide = slideEls[i]; i++) {
  481. var items = slide.querySelectorAll('.build > *');
  482. for (var j = 0, item; item = items[j]; j++) {
  483. if (item.classList) {
  484. item.classList.add('to-build');
  485. }
  486. }
  487. }
  488. };
  489. function handleDomLoaded() {
  490. slideEls = document.querySelectorAll('section.slides > article');
  491. setupFrames();
  492. // addFontStyle();
  493. addGeneralStyle();
  494. // addPrettify();
  495. addEventListeners();
  496. updateSlides();
  497. setupInteraction();
  498. makeBuildLists();
  499. document.body.classList.add('loaded');
  500. };
  501. function initialize() {
  502. getCurSlideFromHash();
  503. if (window['_DEBUG']) {
  504. PERMANENT_URL_PREFIX = '../';
  505. }
  506. if (window['_DCL']) {
  507. handleDomLoaded();
  508. } else {
  509. document.addEventListener('DOMContentLoaded', handleDomLoaded, false);
  510. }
  511. }
  512. // If ?debug exists then load the script relative instead of absolute
  513. if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) {
  514. document.addEventListener('DOMContentLoaded', function() {
  515. // Avoid missing the DomContentLoaded event
  516. window['_DCL'] = true
  517. }, false);
  518. window['_DEBUG'] = true;
  519. var script = document.createElement('script');
  520. script.type = 'text/javascript';
  521. script.src = '../slides.js';
  522. var s = document.getElementsByTagName('script')[0];
  523. s.parentNode.insertBefore(script, s);
  524. // Remove this script
  525. s.parentNode.removeChild(s);
  526. } else {
  527. initialize();
  528. }