slides.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. // Copyright 2012 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. var PERMANENT_URL_PREFIX = '/static/';
  5. var SLIDE_CLASSES = ['far-past', 'past', 'current', 'next', 'far-next'];
  6. var PM_TOUCH_SENSITIVITY = 15;
  7. var curSlide;
  8. /* ---------------------------------------------------------------------- */
  9. /* classList polyfill by Eli Grey
  10. * (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */
  11. if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) {
  12. (function (view) {
  13. var
  14. classListProp = "classList"
  15. , protoProp = "prototype"
  16. , elemCtrProto = (view.HTMLElement || view.Element)[protoProp]
  17. , objCtr = Object
  18. strTrim = String[protoProp].trim || function () {
  19. return this.replace(/^\s+|\s+$/g, "");
  20. }
  21. , arrIndexOf = Array[protoProp].indexOf || function (item) {
  22. for (var i = 0, len = this.length; i < len; i++) {
  23. if (i in this && this[i] === item) {
  24. return i;
  25. }
  26. }
  27. return -1;
  28. }
  29. // Vendors: please allow content code to instantiate DOMExceptions
  30. , DOMEx = function (type, message) {
  31. this.name = type;
  32. this.code = DOMException[type];
  33. this.message = message;
  34. }
  35. , checkTokenAndGetIndex = function (classList, token) {
  36. if (token === "") {
  37. throw new DOMEx(
  38. "SYNTAX_ERR"
  39. , "An invalid or illegal string was specified"
  40. );
  41. }
  42. if (/\s/.test(token)) {
  43. throw new DOMEx(
  44. "INVALID_CHARACTER_ERR"
  45. , "String contains an invalid character"
  46. );
  47. }
  48. return arrIndexOf.call(classList, token);
  49. }
  50. , ClassList = function (elem) {
  51. var
  52. trimmedClasses = strTrim.call(elem.className)
  53. , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
  54. ;
  55. for (var i = 0, len = classes.length; i < len; i++) {
  56. this.push(classes[i]);
  57. }
  58. this._updateClassName = function () {
  59. elem.className = this.toString();
  60. };
  61. }
  62. , classListProto = ClassList[protoProp] = []
  63. , classListGetter = function () {
  64. return new ClassList(this);
  65. }
  66. ;
  67. // Most DOMException implementations don't allow calling DOMException's toString()
  68. // on non-DOMExceptions. Error's toString() is sufficient here.
  69. DOMEx[protoProp] = Error[protoProp];
  70. classListProto.item = function (i) {
  71. return this[i] || null;
  72. };
  73. classListProto.contains = function (token) {
  74. token += "";
  75. return checkTokenAndGetIndex(this, token) !== -1;
  76. };
  77. classListProto.add = function (token) {
  78. token += "";
  79. if (checkTokenAndGetIndex(this, token) === -1) {
  80. this.push(token);
  81. this._updateClassName();
  82. }
  83. };
  84. classListProto.remove = function (token) {
  85. token += "";
  86. var index = checkTokenAndGetIndex(this, token);
  87. if (index !== -1) {
  88. this.splice(index, 1);
  89. this._updateClassName();
  90. }
  91. };
  92. classListProto.toggle = function (token) {
  93. token += "";
  94. if (checkTokenAndGetIndex(this, token) === -1) {
  95. this.add(token);
  96. } else {
  97. this.remove(token);
  98. }
  99. };
  100. classListProto.toString = function () {
  101. return this.join(" ");
  102. };
  103. if (objCtr.defineProperty) {
  104. var classListPropDesc = {
  105. get: classListGetter
  106. , enumerable: true
  107. , configurable: true
  108. };
  109. try {
  110. objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
  111. } catch (ex) { // IE 8 doesn't support enumerable:true
  112. if (ex.number === -0x7FF5EC54) {
  113. classListPropDesc.enumerable = false;
  114. objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
  115. }
  116. }
  117. } else if (objCtr[protoProp].__defineGetter__) {
  118. elemCtrProto.__defineGetter__(classListProp, classListGetter);
  119. }
  120. }(self));
  121. }
  122. /* ---------------------------------------------------------------------- */
  123. /* Slide movement */
  124. function hideHelpText() {
  125. $('#help').hide();
  126. };
  127. function getSlideEl(no) {
  128. if ((no < 0) || (no >= slideEls.length)) {
  129. return null;
  130. } else {
  131. return slideEls[no];
  132. }
  133. };
  134. function updateSlideClass(slideNo, className) {
  135. var el = getSlideEl(slideNo);
  136. if (!el) {
  137. return;
  138. }
  139. if (className) {
  140. el.classList.add(className);
  141. }
  142. for (var i in SLIDE_CLASSES) {
  143. if (className != SLIDE_CLASSES[i]) {
  144. el.classList.remove(SLIDE_CLASSES[i]);
  145. }
  146. }
  147. };
  148. function updateSlides() {
  149. if (window.trackPageview) window.trackPageview();
  150. for (var i = 0; i < slideEls.length; i++) {
  151. switch (i) {
  152. case curSlide - 2:
  153. updateSlideClass(i, 'far-past');
  154. break;
  155. case curSlide - 1:
  156. updateSlideClass(i, 'past');
  157. break;
  158. case curSlide:
  159. updateSlideClass(i, 'current');
  160. break;
  161. case curSlide + 1:
  162. updateSlideClass(i, 'next');
  163. break;
  164. case curSlide + 2:
  165. updateSlideClass(i, 'far-next');
  166. break;
  167. default:
  168. updateSlideClass(i);
  169. break;
  170. }
  171. }
  172. triggerLeaveEvent(curSlide - 1);
  173. triggerEnterEvent(curSlide);
  174. window.setTimeout(function() {
  175. // Hide after the slide
  176. disableSlideFrames(curSlide - 2);
  177. }, 301);
  178. enableSlideFrames(curSlide - 1);
  179. enableSlideFrames(curSlide + 2);
  180. updateHash();
  181. };
  182. function prevSlide() {
  183. hideHelpText();
  184. if (curSlide > 0) {
  185. curSlide--;
  186. updateSlides();
  187. }
  188. };
  189. function nextSlide() {
  190. hideHelpText();
  191. if (curSlide < slideEls.length - 1) {
  192. curSlide++;
  193. updateSlides();
  194. }
  195. };
  196. /* Slide events */
  197. function triggerEnterEvent(no) {
  198. var el = getSlideEl(no);
  199. if (!el) {
  200. return;
  201. }
  202. var onEnter = el.getAttribute('onslideenter');
  203. if (onEnter) {
  204. new Function(onEnter).call(el);
  205. }
  206. var evt = document.createEvent('Event');
  207. evt.initEvent('slideenter', true, true);
  208. evt.slideNumber = no + 1; // Make it readable
  209. el.dispatchEvent(evt);
  210. };
  211. function triggerLeaveEvent(no) {
  212. var el = getSlideEl(no);
  213. if (!el) {
  214. return;
  215. }
  216. var onLeave = el.getAttribute('onslideleave');
  217. if (onLeave) {
  218. new Function(onLeave).call(el);
  219. }
  220. var evt = document.createEvent('Event');
  221. evt.initEvent('slideleave', true, true);
  222. evt.slideNumber = no + 1; // Make it readable
  223. el.dispatchEvent(evt);
  224. };
  225. /* Touch events */
  226. function handleTouchStart(event) {
  227. if (event.touches.length == 1) {
  228. touchDX = 0;
  229. touchDY = 0;
  230. touchStartX = event.touches[0].pageX;
  231. touchStartY = event.touches[0].pageY;
  232. document.body.addEventListener('touchmove', handleTouchMove, true);
  233. document.body.addEventListener('touchend', handleTouchEnd, true);
  234. }
  235. };
  236. function handleTouchMove(event) {
  237. if (event.touches.length > 1) {
  238. cancelTouch();
  239. } else {
  240. touchDX = event.touches[0].pageX - touchStartX;
  241. touchDY = event.touches[0].pageY - touchStartY;
  242. event.preventDefault();
  243. }
  244. };
  245. function handleTouchEnd(event) {
  246. var dx = Math.abs(touchDX);
  247. var dy = Math.abs(touchDY);
  248. if ((dx > PM_TOUCH_SENSITIVITY) && (dy < (dx * 2 / 3))) {
  249. if (touchDX > 0) {
  250. prevSlide();
  251. } else {
  252. nextSlide();
  253. }
  254. }
  255. cancelTouch();
  256. };
  257. function cancelTouch() {
  258. document.body.removeEventListener('touchmove', handleTouchMove, true);
  259. document.body.removeEventListener('touchend', handleTouchEnd, true);
  260. };
  261. /* Preloading frames */
  262. function disableSlideFrames(no) {
  263. var el = getSlideEl(no);
  264. if (!el) {
  265. return;
  266. }
  267. var frames = el.getElementsByTagName('iframe');
  268. for (var i = 0, frame; frame = frames[i]; i++) {
  269. disableFrame(frame);
  270. }
  271. };
  272. function enableSlideFrames(no) {
  273. var el = getSlideEl(no);
  274. if (!el) {
  275. return;
  276. }
  277. var frames = el.getElementsByTagName('iframe');
  278. for (var i = 0, frame; frame = frames[i]; i++) {
  279. enableFrame(frame);
  280. }
  281. };
  282. function disableFrame(frame) {
  283. frame.src = 'about:blank';
  284. };
  285. function enableFrame(frame) {
  286. var src = frame._src;
  287. if (frame.src != src && src != 'about:blank') {
  288. frame.src = src;
  289. }
  290. };
  291. function setupFrames() {
  292. var frames = document.querySelectorAll('iframe');
  293. for (var i = 0, frame; frame = frames[i]; i++) {
  294. frame._src = frame.src;
  295. disableFrame(frame);
  296. }
  297. enableSlideFrames(curSlide);
  298. enableSlideFrames(curSlide + 1);
  299. enableSlideFrames(curSlide + 2);
  300. };
  301. function setupInteraction() {
  302. /* Clicking and tapping */
  303. var el = document.createElement('div');
  304. el.className = 'slide-area';
  305. el.id = 'prev-slide-area';
  306. el.addEventListener('click', prevSlide, false);
  307. document.querySelector('section.slides').appendChild(el);
  308. var el = document.createElement('div');
  309. el.className = 'slide-area';
  310. el.id = 'next-slide-area';
  311. el.addEventListener('click', nextSlide, false);
  312. document.querySelector('section.slides').appendChild(el);
  313. /* Swiping */
  314. document.body.addEventListener('touchstart', handleTouchStart, false);
  315. }
  316. /* Hash functions */
  317. function getCurSlideFromHash() {
  318. var slideNo = parseInt(location.hash.substr(1));
  319. if (slideNo) {
  320. curSlide = slideNo - 1;
  321. } else {
  322. curSlide = 0;
  323. }
  324. };
  325. function updateHash() {
  326. location.replace('#' + (curSlide + 1));
  327. };
  328. /* Event listeners */
  329. function handleBodyKeyDown(event) {
  330. // If we're in a code element, only handle pgup/down.
  331. var inCode = event.target.classList.contains("code");
  332. switch (event.keyCode) {
  333. case 72: // 'H' hides the help text
  334. case 27: // escape key
  335. if (!inCode) hideHelpText();
  336. break;
  337. case 39: // right arrow
  338. case 13: // Enter
  339. case 32: // space
  340. if (inCode) break;
  341. case 34: // PgDn
  342. nextSlide();
  343. event.preventDefault();
  344. break;
  345. case 37: // left arrow
  346. case 8: // Backspace
  347. if (inCode) break;
  348. case 33: // PgUp
  349. prevSlide();
  350. event.preventDefault();
  351. break;
  352. case 40: // down arrow
  353. if (inCode) break;
  354. nextSlide();
  355. event.preventDefault();
  356. break;
  357. case 38: // up arrow
  358. if (inCode) break;
  359. prevSlide();
  360. event.preventDefault();
  361. break;
  362. }
  363. };
  364. function addEventListeners() {
  365. document.addEventListener('keydown', handleBodyKeyDown, false);
  366. };
  367. /* Initialization */
  368. function addFontStyle() {
  369. var el = document.createElement('link');
  370. el.rel = 'stylesheet';
  371. el.type = 'text/css';
  372. el.href = '//fonts.googleapis.com/css?family=' +
  373. 'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono';
  374. document.body.appendChild(el);
  375. };
  376. function addGeneralStyle() {
  377. var el = document.createElement('link');
  378. el.rel = 'stylesheet';
  379. el.type = 'text/css';
  380. el.href = PERMANENT_URL_PREFIX + 'styles.css';
  381. document.body.appendChild(el);
  382. var el = document.createElement('meta');
  383. el.name = 'viewport';
  384. el.content = 'width=1100,height=750';
  385. document.querySelector('head').appendChild(el);
  386. var el = document.createElement('meta');
  387. el.name = 'apple-mobile-web-app-capable';
  388. el.content = 'yes';
  389. document.querySelector('head').appendChild(el);
  390. };
  391. function showHelpText() {
  392. };
  393. function handleDomLoaded() {
  394. slideEls = document.querySelectorAll('section.slides > article');
  395. setupFrames();
  396. addFontStyle();
  397. addGeneralStyle();
  398. addEventListeners();
  399. updateSlides();
  400. setupInteraction();
  401. if (window.location.hostname == "localhost" || window.location.hostname == "127.0.0.1" || window.location.hostname == "::1") {
  402. hideHelpText();
  403. }
  404. document.body.classList.add('loaded');
  405. };
  406. function initialize() {
  407. getCurSlideFromHash();
  408. if (window['_DEBUG']) {
  409. PERMANENT_URL_PREFIX = '../';
  410. }
  411. if (window['_DCL']) {
  412. handleDomLoaded();
  413. } else {
  414. document.addEventListener('DOMContentLoaded', handleDomLoaded, false);
  415. }
  416. }
  417. // If ?debug exists then load the script relative instead of absolute
  418. if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) {
  419. document.addEventListener('DOMContentLoaded', function() {
  420. // Avoid missing the DomContentLoaded event
  421. window['_DCL'] = true
  422. }, false);
  423. window['_DEBUG'] = true;
  424. var script = document.createElement('script');
  425. script.type = 'text/javascript';
  426. script.src = '../slides.js';
  427. var s = document.getElementsByTagName('script')[0];
  428. s.parentNode.insertBefore(script, s);
  429. // Remove this script
  430. s.parentNode.removeChild(s);
  431. } else {
  432. initialize();
  433. }