github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/website/talks/2011-05-07-Camlistore-Sao-Paolo/slides.js (about)

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