Source: annotator.ui.js

/******************************************************************************
 * annotator.js
 *
 * Contains the frontend logic of the annotator.
 *
 *****************************************************************************/

/* global Annotator, Rainbow */

/** @namespace Annotator */
var Annotator = Annotator || {};

/**
 *  The UI logic.
 *
 *  @memberof Annotator
 *  @namespace Annotator.UI
 */
Annotator.UI = (function(){

  'use strict';

  var

    /**
     *  Wrapper element for the annotator app.
     *
     *  @private
     *  @memberof Annotator.UI
     *  @inner
     *  @type {Element} 
     */
    annotatorElm,

    /* @type {boolean} */
    userSetUrlHash,

    /* @type {Element} */
    nav,
    /* @type {Element} */
    form,
    /* @type {Element} */
    goBtn,
    /* @type {Element} */
    nextBtn,
    /* @type {Element} */
    helpBtn,
    /* @type {Element} */
    pauseBtn,
    /* @type {Element} */
    statusBtn,
    /* @type {Element} */
    neutralBtn,
    /* @type {Element} */
    helpContent,
    /* @type {Element} */
    previousBtn,
    /* @type {Element} */
    tweetContent,
    /* @type {Element} */
    closeHelpBtn,
    /* @type {Element} */
    irrelevantBtn,
    /* @type {Element} */
    opinionatedBtn,

    /* @type {object} */
    KeyCodes = {
      q:81,
      e:69,
      w:87,
      a:65,
      s:83,
      c:67,
      d:68
    };

  /* Action Handler -------------------------------------------------------- */

  /**
   *  Keyboard event handler for handling key pressing down.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {KeyboardEvent} event - keydown event object
   *  @return {undefined}
   */
  function Document_KeyDownHandler (event) {
    if (event.keyCode === KeyCodes.w) {
      ShowButtonPress(helpBtn);
    } else if (!annotatorElm.classList.contains('pause')) {
      switch(event.keyCode){
        case KeyCodes.q : ShowButtonPress(previousBtn);    break;
        case KeyCodes.e : ShowButtonPress(nextBtn);        break;
        case KeyCodes.a : ShowButtonPress(irrelevantBtn);  break;
        case KeyCodes.s : ShowButtonPress(neutralBtn);     break;
        case KeyCodes.d : ShowButtonPress(opinionatedBtn); break;
        case KeyCodes.c : ShowButtonPress(pauseBtn);
      }
    } else if (annotatorElm.classList.contains('pause') && event.keyCode == KeyCodes.c) {
      ShowButtonPress(goBtn);
    }
  }

  /**
   *  Keyboard event handler for handling key releasing.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {KeyboardEvent} event - keyup event object
   *  @return {undefined}
   */
  function Document_KeyUpHandler (event) {
    ShowButtonPressReset();

    if (event.keyCode === KeyCodes.w) {
      ToggleHelp();
    } else if (!annotatorElm.classList.contains('pause') && !helpContent.classList.contains('open')) {
      switch(event.keyCode){
        case KeyCodes.q : GetTweet(Annotator.IO.PreviousTweet); break;
        case KeyCodes.e : GetTweet(Annotator.IO.NextTweet);     break;
        case KeyCodes.a : SendAnnotation('Irrelevant');         break;
        case KeyCodes.s : SendAnnotation('Neutral');            break;
        case KeyCodes.d : SendAnnotation('Opinionated');        break;
        case KeyCodes.c : SendPause();
      }
    } else if (annotatorElm.classList.contains('pause') && event.keyCode == KeyCodes.c) {
      SendResume();
    }
  }

  /**
   *  Click handler for Button "PauseBtn".
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}    
   */
  function PauseBtn_ClickHandler () {
    pauseBtn.blur();
    SendPause();
  }

  /**
   *  Click handler for Button "GoBtn".
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}    
   */
  function GoBtn_ClickHandler () {
    goBtn.blur();
    SendResume();
  }

  /**
   *  Click handler for Button "PreviousBtn".
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}    
   */
  function PreviousBtn_ClickHandler () {
    previousBtn.blur();
    GetTweet(Annotator.IO.PreviousTweet);
  }

  /**
   *  Click handler for Button "NextBtn".
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}    
   */
  function NextBtn_ClickHandler () {
    nextBtn.blur();
    GetTweet(Annotator.IO.NextTweet);
  }

  /**
   *  Click handler for Button "HelpBtn".
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}    
   */
  function HelpBtn_ClickHandler () {
    helpBtn.blur();
    if (!helpBtn.classList.contains('open')) {
      ToggleHelp();
    }
  }

  /**
   *  Submit handler for annotator form.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {Event} event - submit event
   *  @return {undefined}    
   */
  function Form_SubmitHandler (event) {
    event.preventDefault();
  }

  /**
   *  Click handler for Button "NeutralBtn".
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}    
   */
  function NeutralBtn_ClickHanlder () {
    neutralBtn.blur();
    SendAnnotation('Neutral');
  }

  /**
   *  Click handler for Button "IrrelevantBtn".
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}
   */
  function IrrelevantBtn_ClickHanlder () {
    irrelevantBtn.blur();
    SendAnnotation('Irrelevant');
  }

  /**
   *  Click handler for Button "OpinionatedBtn".
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}
   */
  function OpinionatedBtn_ClickHanlder () {
    opinionatedBtn.blur();
    SendAnnotation('Opinionated');
  }

  /**
   *  Shows button press state by toggle active class.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {Element} elm - the element
   *  @return {undefined}
   */
  function ShowButtonPress (elm) {
    elm.classList.toggle('active');
  }

  /**
   *  Resets button press state for all buttons by toggling the active class off.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}
   */
  function ShowButtonPressReset () {
    [
      goBtn,
      nextBtn,
      helpBtn,
      pauseBtn,
      neutralBtn,
      previousBtn,
      irrelevantBtn,
      opinionatedBtn
    ].forEach(function (b) { 
      b.classList.remove('active');
    });
  }

  /* HELP ------------------------------------------------------------------ */

  /**
   *  Open or close the help.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}
   */
  function ToggleHelp () {
    helpContent.classList.toggle('open');
  }

  /* IO -------------------------------------------------------------------- */

  /**
   *  Send pause to server.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}
   */
  function SendPause () {
    SetTweet('');
    annotatorElm.classList.add('loading');
    Annotator.IO.Pause(function (json) {
      if (json.success) {
        goBtn.querySelector('span').innerHTML = 'Continue'; 
        annotatorElm.classList.remove('loading');
        annotatorElm.classList.add('pause');
      }
    });
  }

  /**
   *  Send resume to server.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}
   */
  function SendResume () {
    annotatorElm.classList.remove('pause');
    annotatorElm.classList.add('loading');
    Annotator.IO.Resume(AnnotatorIO_TweetHandler);
  }

  /**
   *  Send annotation of tweet to server.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {string} annotation - chosen text
   *  @return {undefined}
   */
  function SendAnnotation (annotation) {
    annotatorElm.classList.remove('pause');
    annotatorElm.classList.add('loading');
    Annotator.IO.SendAnnotation(annotation, AnnotatorIO_TweetHandler);
  }

  /**
   *  Get next or previous tweet helper function.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {function} ioCall - next or previous tweet io call.
   *  @return {undefined}
   */
  function GetTweet (ioCall) {
    annotatorElm.classList.remove('pause');
    annotatorElm.classList.add('loading');
    ioCall(AnnotatorIO_TweetHandler);
  }

  /**
   *  Gets a specific tweet for annotation by number. 
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {number} num - tweet number
   *  @return {undefined}
   */
  function GetTweetByNumber (num) {
    annotatorElm.classList.remove('pause');
    annotatorElm.classList.add('loading');
    Annotator.IO.GetTweet(num, AnnotatorIO_TweetHandler);
  }

  /**
   *  Handling io calls. 
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {ioCallJsonResult} json - result of io call
   *  @return {undefined}
   */
  function AnnotatorIO_TweetHandler (json) {
    if (json.success) {
      
      form.classList.remove('Neutral', 'Irrelevant', 'Opinionated');

      if (json.chosen) {
        form.classList.add(json.chosen);
      }
      
      SetTweet(json.tweet);
      SetNavigation(json);
      SetStatus(json);
      annotatorElm.classList.remove('loading');

      if (json.countAnnotatedTweets == json.countTweets) {
        annotatorElm.classList.add('finish');
      } 
    }
  }

  /**
   *  Set current tweet text.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {string} tweet - tweet text
   *  @return {undefined}
   */
  function SetTweet (tweet) {
    Rainbow.color(tweet, 'tweet', function (colored_tweet) {
      tweetContent.innerHTML = colored_tweet;
    });
  }

  /* Status ---------------------------------------------------------------- */

  /**
   *  Set statistics of annotation, like how many are annotated.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {ioCallJsonResult} json - result of io call 
   *  @return {undefined}
   */
  function SetStatus (json) {
    statusBtn.innerHTML = json.countAnnotatedTweets + '/' + json.countTweets;
  }

  /* URL Hash -------------------------------------------------------------- */

  /**
   *  Set browser navigation.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {ioCallJsonResult} json - result of io call 
   *  @return {undefined}
   */
  function SetNavigation (json) {
    SetUrlHash(json.currentTweetNum);
    SetTitle(json.currentTweetNum);
    SetNextPreviousButton(json.hasNext, json.hasPrevious);
  }

  /**
   *  Set browser URL-hash.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {number} currentTweetNum - current number of tweet
   *  @return {undefined}
   */
  function SetUrlHash (currentTweetNum) {
    if (window.location.hash != '#' + currentTweetNum.toString()) {
      userSetUrlHash = false;
      window.location.hash = currentTweetNum;
    }
  }

  /**
   *  Set website title.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {number} currentTweetNum - current number of tweet
   *  @return {undefined}
   */
  function SetTitle (currentTweetNum) {
    document.title = 'Twitter Annotator - Tweet ' + currentTweetNum;
  }

  /**
   *  Set button state for next and previous tweet.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @param {boolean} hasNext - has next tweet to annotate
   *  @param {boolean} hasPrevious - has previous tweet to annotate
   *  @return {undefined}
   */
  function SetNextPreviousButton (hasNext, hasPrevious) {
    
    if (hasNext) {
      nav.classList.add('hasNext');
      nextBtn.tabIndex = 3;
      
    } else {
      nav.classList.remove('hasNext');
      nextBtn.tabIndex = -1;
    }

    if (hasPrevious){
      nav.classList.add('hasPrevious');
      previousBtn.tabIndex = 1;
    } else {
      nav.classList.remove('hasPrevious');
      previousBtn.tabIndex = -1;
    }
  }

  /**
   *  Handler for window 'hashchange' event.
   *
   *  @private
   *  @memberof Annotator.UI
   *  @inner
   *  @return {undefined}
   */
  function Window_HashchangeHandler () {
    if(userSetUrlHash && window.location.hash) {
      var hash = window.location.hash.substring(1);
      GetTweetByNumber(parseInt(hash, 10));
    }
    userSetUrlHash = true;
  }

  /* Interface ------------------------------------------------------------- */

  return {

    /**
     *  Initialize annotator UI.
     *  
     *  @public
     *  @memberof Annotator.UI
     *  @example 
     *    Annotator.UI.Run();
     *  @return {undefined}
     */
    Run: function () {

      userSetUrlHash = true;

      annotatorElm = document.querySelector('.annotator');

      nav = annotatorElm.querySelector('nav');
      form = annotatorElm.querySelector('form');
      goBtn = annotatorElm.querySelector('.goBtn');
      nextBtn = annotatorElm.querySelector('.nextBtn');
      helpBtn = annotatorElm.querySelector('.helpBtn');
      pauseBtn = annotatorElm.querySelector('.pauseBtn');
      statusBtn =  annotatorElm.querySelector('.statusBtn');
      neutralBtn = annotatorElm.querySelector('.neutralBtn');
      helpContent = annotatorElm.querySelector('.helpContent');
      previousBtn = annotatorElm.querySelector('.previousBtn');
      tweetContent = annotatorElm.querySelector('.tweetContent');
      closeHelpBtn = annotatorElm.querySelector('.closeHelpBtn');
      irrelevantBtn = annotatorElm.querySelector('.irrelevantBtn');
      opinionatedBtn = annotatorElm.querySelector('.opinionatedBtn');

      form.addEventListener('submit', Form_SubmitHandler);
      goBtn.addEventListener('click', GoBtn_ClickHandler);
      nextBtn.addEventListener('click', NextBtn_ClickHandler);
      helpBtn.addEventListener('click', HelpBtn_ClickHandler);
      pauseBtn.addEventListener('click', PauseBtn_ClickHandler);
      neutralBtn.addEventListener('click', NeutralBtn_ClickHanlder);
      previousBtn.addEventListener('click', PreviousBtn_ClickHandler);
      closeHelpBtn.addEventListener('click', HelpBtn_ClickHandler);
      irrelevantBtn.addEventListener('click', IrrelevantBtn_ClickHanlder);
      opinionatedBtn.addEventListener('click', OpinionatedBtn_ClickHanlder);

      document.addEventListener('keydown', Document_KeyDownHandler);
      document.addEventListener('keyup', Document_KeyUpHandler);

      window.addEventListener('hashchange', Window_HashchangeHandler);

      Window_HashchangeHandler();
    }
  };

}());