<commit_before>// PLUGIN: Quiz

(function ( Popcorn ) {

  var QUIZ_NUMBER = 0,
      NO_ANSWER = -1,
      weight = function( correctAnswer ) {
        return ( correctAnswer === NO_ANSWER ) ? 0 : 1;
      };

  Popcorn.plugin( "quiz", function( options ) {
    function Quiz() {
      var currentIndex = 0,
          that = this;

      function decoratable( funcName ) {
        var result,
        decorators = that.decoratorsList[ funcName ] || [],
        args = Array.prototype.slice.call( arguments, 1 );

        for ( var idx = 0; idx < decorators.length; idx++ ) {
          result = Quiz.decorators[ decorators[ idx ] ].prepare.apply( null, args );
        }

        return result;
      }

      this.decoratorsList = {};

      this.decorate = function( funcName, decorator ) {
        if ( !this.decoratorsList[ funcName ] ) {
          this.decoratorsList[ funcName ] = [];
        }
        this.decoratorsList[ funcName ].push( decorator );
      };

      function toggle( quizQuestion ) {
        hideAll();
        document.getElementById( "questionContainer-" + quizNumber + "-" + quizQuestion ).style.display = "inline";
      }

      this.prepare = function( target ) {
        var questionContainer, answerRadioButton, answerLabel, p, ul, li;
        for ( var questionIdx = 0; questionIdx < questions.length; questionIdx++ ) {
          var theQuestion = questions[ questionIdx ],
          answers = theQuestion.answers,
          radioButtonGuid = "answer-" + Popcorn.guid(),
          id;

          questionContainer = document.createElement( "div" );
          p = document.createElement( "p" );
          ul = document.createElement( "ul" );
          ul.setAttribute( "class", "popcorn-question-list" );

          // SET OTHER CLASSES
          // Popcorn.extend( questionContainer, {
          //   "class": "popcorn-question-container",
          //   id: "questionContainer-" + questionIdx
          // });
          Popcorn.extend( questionContainer, {
            "class": "popcorn-question-container",
            id: "questionContainer-" + quizNumber + "-" + questionIdx
          });

          // questionContainer.style.display = "none";

          // decoratable( "prepare", questionContainer );

          p.innerHTML = theQuestion.question;

          questionContainer.appendChild( p );

          for ( var idx = 0; idx < answers.length; idx++ ) {
            li = document. createElement( "li" );
            id = "answer-" + quizNumber + "-" + questionIdx + "-" + idx;

            answerRadioButton = document.createElement( "input" );
            Popcorn.extend( answerRadioButton, {
              "id": id,
              name: radioButtonGuid,
              type: "radio"
            });

            answerRadioButton.addEventListener( "click", function( questionIdx, answer ) {
              return function() {
                selectedAnswers[ questionIdx ] = answer;
              };
            }( questionIdx, answers[ idx ] ), false );

            answerLabel = document.createElement( "label" );
            answerLabel.innerHTML = answers[ idx ];
            answerLabel.setAttribute( "for", id );

            li.appendChild( answerRadioButton );
            li.appendChild( answerLabel );
            ul.appendChild( li );
            questionContainer.appendChild( ul );
            options._container.appendChild( questionContainer );
          }
        }
      };

      this.hasPrevious = function() {
        if ( !paginated ) {
          return false;
        }
        return currentIndex > 0;
      };

      this.hasNext = function() {
        if ( !paginated ) {
          return false;
        }
        return currentIndex < questions.length - 1;
      };

      this.next = function() {
        if ( this.hasNext() ) {
          currentIndex = currentIndex + 1;
          toggle( currentIndex );
          return questions[ currentIndex ];
        }
      };

      this.previous = function() {
        if ( this.hasPrevious() ) {
          currentIndex = currentIndex - 1;
          toggle( currentIndex );
          return questions[ currentIndex ];
        }
      };

      this.start = function() {
        if ( !paginated ) {
          showAll();
          return;
        }
        currentIndex = 0;
        questions.length > 0 && toggle( currentIndex );
        // return questions[ currentIndex ];
      };

      this.end = function( target ) {
        var resumeButton = addButton( "resumeButton", "Resume", function() {
          options._parentContainer.style.display = "none";
          popcorn.play();
        }),
        resumeText = document.createElement( "p" );

        resumeText.innerHTML = "Quiz is complete!";

        function checkAnswers() {
          var theQuestion, correctAnswer, selectedAnswer, score;

          correctAnswers = 0;

          for ( var questionIdx = 0; questionIdx < questions.length; questionIdx++ ) {
            theQuestion = questions[ questionIdx ];
            correctAnswer = theQuestion.correctAnswer;
            selectedAnswer = selectedAnswers[ questionIdx ];
            // what if nothing is selected?
            if ( correctAnswer === NO_ANSWER || selectedAnswer && selectedAnswer === theQuestion.answers[ correctAnswer ] ) {
              score = 1 * weight( correctAnswer );
              correctAnswers = correctAnswers + score;
              theQuestion.isCorrect = true;
              theQuestion.correctAnswerCallback && theQuestion.correctAnswerCallback( selectedAnswer );
            }
            else {
              theQuestion.isCorrect = false;
              theQuestion.wrongAnswerCallback && theQuestion.wrongAnswerCallback();
            }
          }
        }

        checkAnswers();

        if ( paginated ) {
          hideAll();
        }

        decoratable( "end", target );

        target.appendChild( resumeText );
        target.appendChild( resumeButton );

        quizComplete = true;
      };
    }

    Quiz.decorators = {};

    Quiz.decorators.correctAnswers = {
      prepare: function( target ) {
        document.getElementById( "popcorn-question-result" ) && target.removeChild( document.getElementById( "popcorn-quiz-results" ) );
        var resultsDiv = document.createElement( "div" );
        // hideAll();
        resultsDiv.setAttribute( "id", "popcorn-quiz-results" );
        resultsDiv.appendChild( document.createTextNode( "You got " + correctAnswers + " / " + questionable + " correct answers!" ) );
        target.appendChild( resultsDiv );
      }
    };

    Quiz.decorators.results = {
      prepare: function( target ) {
        var resultsDiv = document.createElement( "div" );

        function checkAnswers() {
          var correctAnswerDiv, correctAnswerDivId, theQuestion, correctAnswer, selectedAnswer, questionContainer;

          for ( var questionIdx = 0; questionIdx < questions.length; questionIdx++ ) {
            correctAnswerDiv = document.createElement( "div" );
            correctAnswerDivId = "popcorn-correct-answer-" + quizNumber + "-" + questionIdx;
            theQuestion = questions[ questionIdx ];
            correctAnswer = theQuestion.correctAnswer;
            selectedAnswer = selectedAnswers[ questionIdx ];
            // document.getElementById( "answer-" + quizNumber + "-" + questionIdx + "-" + correctAnswer ).parentNode.setAttribute( "class", "" );
            // what if nothing is selected?
            if ( !theQuestion.isCorrect ) {
              if ( !paginated ) {
                var p = document.createElement( "p" );
                questionContainer = document.getElementById( "questionContainer-" + quizNumber + "-" + questionIdx );
                document.getElementById( correctAnswerDivId ) && questionContainer.removeChild( document.getElementById( correctAnswerDivId ) );
                p.innerHTML = "Correct Answer: " + theQuestion.answers[ correctAnswer ];
                correctAnswerDiv.appendChild( p );

                if ( theQuestion.explanation ) {
                  p = document.createElement( "p" );
                  p.innerHTML = "Explanation: " + theQuestion.explanation;
                  correctAnswerDiv.appendChild( p );
                }

                correctAnswerDiv.setAttribute( "id", correctAnswerDivId );
                questionContainer.appendChild( correctAnswerDiv );

                // TODO: Add to class list?
                document.getElementById( "answer-" + quizNumber + "-" + questionIdx + "-" + correctAnswer ).parentNode.setAttribute( "class", "popcorn-correct-answer" );
              }
            }
          }
        }

        checkAnswers();

        document.getElementById( "popcorn-quiz-" + quizNumber + "-results" ) && target.removeChild( document.getElementById( "popcorn-quiz-" + quizNumber + "-results" ) );
        // hideAll();
        resultsDiv.setAttribute( "id", "popcorn-quiz-" + quizNumber + "-results" );
        resultsDiv.appendChild( document.createTextNode( "You got " + correctAnswers + " / " + questionable + " correct answers!" ) );
        target.appendChild( resultsDiv );
      }
    };

    Quiz.decorators.paginate = {
      prepare: function( target ) {
        target.style.display = "none";
      }
    };

    function showAll() {
      for ( var idx=0; idx<questions.length; idx++ ) {
        document.getElementById( "questionContainer-" + quizNumber + "-" + idx ).style.display = "inline";
      }
    }

    function hideAll() {
      for ( var idx=0; idx<questions.length; idx++ ) {
        document.getElementById( "questionContainer-" + quizNumber + "-" + idx ).style.display = "none";
      }
    }

    function addButton( buttonName, buttonText, clickCallback ) {
      var button = buttons[ buttonName ];

      if ( !button ) {
        button = document.createElement( "button" );
        button.appendChild( document.createTextNode( buttonText ) );
        button.addEventListener( "click", function() {
          clickCallback();
        }, false );
        buttons[ buttonName ] = button;
        options._container.appendChild( button );
      }
      return button;
    }

    // end = end || start + 0.5,
    // UL > LI elements?
    var popcorn = this,
        questions = options.questions || [],
        // allowPause = options.allowPause === false ? false : true,
        allowPause = options.allowPause = typeof allowPause === "undefined" ? true : !!options.allowPause,
        continueButtonText = options.continueButtonText || "Continue",
        previousButtonText = options.previousButtonText || "Previous",
        quiz = new Quiz(),
        selectedAnswers = {},
        buttons = {},
        correctAnswers = 0,
        quizNumber = QUIZ_NUMBER++,
        questionable = 0,
        quizComplete = false,
        paginated;

    for ( var idx = 0; idx < questions.length; idx++ ) {
      ( questions[ idx ].correctAnswer !== NO_ANSWER ) && questionable++;
    }

    return {
      _setup: function( options ) {
        var target = document.getElementById( options.target ),
            title = document.createElement( "div" );

        title.classList.add( "quiz-title" );
        paginated = options.paginate = typeof options.paginate === "undefined" ? true : !!options.paginate;

        options.decorators = options.decorators || [];
        options.title = options.title || "Quiz";

        title.innerHTML = options.title;

        options._container = document.createElement( "div" );
        // options._container.style.display = "none";

        // FIXME: make this options._container?
        options._parentContainer = document.createElement( "div" );
        options._parentContainer.style.display = "none";
        options._parentContainer.classList.add( "quiz-parent-container" );
        options._parentContainer.appendChild( title );
        options._parentContainer.appendChild( options._container );

        for ( var idx = 0; idx < options.decorators.length; idx++ ) {
          var decoratorOptions = options.decorators[ idx ];
          quiz.decorate( decoratorOptions.when, decoratorOptions.what );
        }

        // paginated && quiz.decorate( "prepare", "paginate" );

        // quiz.decorate( "end", "results" );
        // TODO: IS THIS RIGHT? or pass in target?
        quiz.prepare( options._container );

        if ( !target && Popcorn.plugin.debug ) {
          throw new Error( "target container doesn't exist" );
        }

        // FIXME: This is a work around until a bug is fixed in Popcorn Maker
        // Bug is not filed yet, but it has to do with the default Ids in editor's being "Media Element" (not supposed to have spaces in Ids)
        if ( target === popcorn.media.parentNode.parentNode ) {
          options._container.classList.add( "quiz-question-container" );
        }

        // if ( !options.question || !options.answers && Popcorn.plugin.debug ) {
        //   throw new Error( "a quiz needs a question and possible answers" );
        // }
        target && target.appendChild( options._parentContainer );
      },
      start: function( event, options ) {
        function toggleButtons() {
          var previousButton = addButton( "previousButton", previousButtonText, function() {
                quiz.previous();
                previousButton.style.display = quiz.hasPrevious() ? "inline" : "none";
              }),
              continueButton = addButton( "continueButton", continueButtonText, function() {
                if ( quiz.hasNext() ) {
                  quiz.next();
                  previousButton.style.display = "inline";
                }
                else {
                  quiz.end( options._container );
                  continueButton.style.display = "none";
                  previousButton.style.display = "none";
                }
              });

          continueButton.style.display = "inline";
          previousButton.style.display = "none";
        }

        if ( allowPause ) {
          popcorn.pause();
        }

        options._parentContainer.style.display = "inline";
        if ( !quizComplete ) {
          quiz.start();
          toggleButtons();
        }
      },
      end: function( event, options ) {
        options._parentContainer.style.display = "none";
      },
      _teardown: function( options ) {
        document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._parentContainer );
      }
    };
  },
  {
    about: {
      name: "Popcorn Quiz Plugin",
      version: "0.1",
      author: "Brian Chirls, @bchirls",
      website: "http://github.com/brianchirls"
    },
    options: {
      questions: {
        elem: "input",
        type: "textarea",
        label: "Question"
      },
      paginate: {
        elem: "input",
        type: "checkbox",
        label: "Paginate",
        "default": true
      },
      decorators: {
        elem: "input",
        type: "textarea",
        label: "Show Results"
      },
      target: "questions",
      start: {
        elem: "input",
        type: "number",
        label: "Start Time"
      },
      end: {
        elem: "input",
        type: "number",
        label: "End Time"
      },
      title: {
        elem: "input",
        type: "textarea",
        label: "Title"
      }
    }
  });
})( Popcorn );
<commit_msg>Emit an event when a quiz ends<commit_after>// PLUGIN: Quiz

(function ( Popcorn ) {

  var QUIZ_NUMBER = 0,
      NO_ANSWER = -1,
      weight = function( correctAnswer ) {
        return ( correctAnswer === NO_ANSWER ) ? 0 : 1;
      };

  Popcorn.plugin( "quiz", function( options ) {
    function Quiz() {
      var currentIndex = 0,
          that = this;

      function decoratable( funcName ) {
        var result,
        decorators = that.decoratorsList[ funcName ] || [],
        args = Array.prototype.slice.call( arguments, 1 );

        for ( var idx = 0; idx < decorators.length; idx++ ) {
          result = Quiz.decorators[ decorators[ idx ] ].prepare.apply( null, args );
        }

        return result;
      }

      this.decoratorsList = {};

      this.decorate = function( funcName, decorator ) {
        if ( !this.decoratorsList[ funcName ] ) {
          this.decoratorsList[ funcName ] = [];
        }
        this.decoratorsList[ funcName ].push( decorator );
      };

      function toggle( quizQuestion ) {
        hideAll();
        document.getElementById( "questionContainer-" + quizNumber + "-" + quizQuestion ).style.display = "inline";
      }

      this.prepare = function( target ) {
        var questionContainer, answerRadioButton, answerLabel, p, ul, li;
        for ( var questionIdx = 0; questionIdx < questions.length; questionIdx++ ) {
          var theQuestion = questions[ questionIdx ],
          answers = theQuestion.answers,
          radioButtonGuid = "answer-" + Popcorn.guid(),
          id;

          questionContainer = document.createElement( "div" );
          p = document.createElement( "p" );
          ul = document.createElement( "ul" );
          ul.setAttribute( "class", "popcorn-question-list" );

          // SET OTHER CLASSES
          // Popcorn.extend( questionContainer, {
          //   "class": "popcorn-question-container",
          //   id: "questionContainer-" + questionIdx
          // });
          Popcorn.extend( questionContainer, {
            "class": "popcorn-question-container",
            id: "questionContainer-" + quizNumber + "-" + questionIdx
          });

          // questionContainer.style.display = "none";

          // decoratable( "prepare", questionContainer );

          p.innerHTML = theQuestion.question;

          questionContainer.appendChild( p );

          for ( var idx = 0; idx < answers.length; idx++ ) {
            li = document. createElement( "li" );
            id = "answer-" + quizNumber + "-" + questionIdx + "-" + idx;

            answerRadioButton = document.createElement( "input" );
            Popcorn.extend( answerRadioButton, {
              "id": id,
              name: radioButtonGuid,
              type: "radio"
            });

            answerRadioButton.addEventListener( "click", function( questionIdx, answer ) {
              return function() {
                selectedAnswers[ questionIdx ] = answer;
              };
            }( questionIdx, answers[ idx ] ), false );

            answerLabel = document.createElement( "label" );
            answerLabel.innerHTML = answers[ idx ];
            answerLabel.setAttribute( "for", id );

            li.appendChild( answerRadioButton );
            li.appendChild( answerLabel );
            ul.appendChild( li );
            questionContainer.appendChild( ul );
            options._container.appendChild( questionContainer );
          }
        }
      };

      this.hasPrevious = function() {
        if ( !paginated ) {
          return false;
        }
        return currentIndex > 0;
      };

      this.hasNext = function() {
        if ( !paginated ) {
          return false;
        }
        return currentIndex < questions.length - 1;
      };

      this.next = function() {
        if ( this.hasNext() ) {
          currentIndex = currentIndex + 1;
          toggle( currentIndex );
          return questions[ currentIndex ];
        }
      };

      this.previous = function() {
        if ( this.hasPrevious() ) {
          currentIndex = currentIndex - 1;
          toggle( currentIndex );
          return questions[ currentIndex ];
        }
      };

      this.start = function() {
        if ( !paginated ) {
          showAll();
          return;
        }
        currentIndex = 0;
        questions.length > 0 && toggle( currentIndex );
        // return questions[ currentIndex ];
      };

      this.end = function( target ) {
        var resumeButton = addButton( "resumeButton", "Resume", function() {
          options._parentContainer.style.display = "none";
          popcorn.play();
        }),
        resumeText = document.createElement( "p" );

        resumeText.innerHTML = "Quiz is complete!";

        function checkAnswers() {
          var theQuestion, correctAnswer, selectedAnswer, score;

          correctAnswers = 0;

          for ( var questionIdx = 0; questionIdx < questions.length; questionIdx++ ) {
            theQuestion = questions[ questionIdx ];
            correctAnswer = theQuestion.correctAnswer;
            selectedAnswer = selectedAnswers[ questionIdx ];
            // what if nothing is selected?
            if ( correctAnswer === NO_ANSWER || selectedAnswer && selectedAnswer === theQuestion.answers[ correctAnswer ] ) {
              score = 1 * weight( correctAnswer );
              correctAnswers = correctAnswers + score;
              theQuestion.isCorrect = true;
              theQuestion.correctAnswerCallback && theQuestion.correctAnswerCallback( selectedAnswer );
            }
            else {
              theQuestion.isCorrect = false;
              theQuestion.wrongAnswerCallback && theQuestion.wrongAnswerCallback();
            }
          }
        }

        checkAnswers();

        if ( paginated ) {
          hideAll();
        }

        decoratable( "end", target );

        target.appendChild( resumeText );
        target.appendChild( resumeButton );

        quizComplete = true;
        popcorn.emit("quizended", {
          score: correctAnswers,
          outOf: questionable
        });
      };
    }

    Quiz.decorators = {};

    Quiz.decorators.correctAnswers = {
      prepare: function( target ) {
        document.getElementById( "popcorn-question-result" ) && target.removeChild( document.getElementById( "popcorn-quiz-results" ) );
        var resultsDiv = document.createElement( "div" );
        // hideAll();
        resultsDiv.setAttribute( "id", "popcorn-quiz-results" );
        resultsDiv.appendChild( document.createTextNode( "You got " + correctAnswers + " / " + questionable + " correct answers!" ) );
        target.appendChild( resultsDiv );
      }
    };

    Quiz.decorators.results = {
      prepare: function( target ) {
        var resultsDiv = document.createElement( "div" );

        function checkAnswers() {
          var correctAnswerDiv, correctAnswerDivId, theQuestion, correctAnswer, selectedAnswer, questionContainer;

          for ( var questionIdx = 0; questionIdx < questions.length; questionIdx++ ) {
            correctAnswerDiv = document.createElement( "div" );
            correctAnswerDivId = "popcorn-correct-answer-" + quizNumber + "-" + questionIdx;
            theQuestion = questions[ questionIdx ];
            correctAnswer = theQuestion.correctAnswer;
            selectedAnswer = selectedAnswers[ questionIdx ];
            // document.getElementById( "answer-" + quizNumber + "-" + questionIdx + "-" + correctAnswer ).parentNode.setAttribute( "class", "" );
            // what if nothing is selected?
            if ( !theQuestion.isCorrect ) {
              if ( !paginated ) {
                var p = document.createElement( "p" );
                questionContainer = document.getElementById( "questionContainer-" + quizNumber + "-" + questionIdx );
                document.getElementById( correctAnswerDivId ) && questionContainer.removeChild( document.getElementById( correctAnswerDivId ) );
                p.innerHTML = "Correct Answer: " + theQuestion.answers[ correctAnswer ];
                correctAnswerDiv.appendChild( p );

                if ( theQuestion.explanation ) {
                  p = document.createElement( "p" );
                  p.innerHTML = "Explanation: " + theQuestion.explanation;
                  correctAnswerDiv.appendChild( p );
                }

                correctAnswerDiv.setAttribute( "id", correctAnswerDivId );
                questionContainer.appendChild( correctAnswerDiv );

                // TODO: Add to class list?
                document.getElementById( "answer-" + quizNumber + "-" + questionIdx + "-" + correctAnswer ).parentNode.setAttribute( "class", "popcorn-correct-answer" );
              }
            }
          }
        }

        checkAnswers();

        document.getElementById( "popcorn-quiz-" + quizNumber + "-results" ) && target.removeChild( document.getElementById( "popcorn-quiz-" + quizNumber + "-results" ) );
        // hideAll();
        resultsDiv.setAttribute( "id", "popcorn-quiz-" + quizNumber + "-results" );
        resultsDiv.appendChild( document.createTextNode( "You got " + correctAnswers + " / " + questionable + " correct answers!" ) );
        target.appendChild( resultsDiv );
      }
    };

    Quiz.decorators.paginate = {
      prepare: function( target ) {
        target.style.display = "none";
      }
    };

    function showAll() {
      for ( var idx=0; idx<questions.length; idx++ ) {
        document.getElementById( "questionContainer-" + quizNumber + "-" + idx ).style.display = "inline";
      }
    }

    function hideAll() {
      for ( var idx=0; idx<questions.length; idx++ ) {
        document.getElementById( "questionContainer-" + quizNumber + "-" + idx ).style.display = "none";
      }
    }

    function addButton( buttonName, buttonText, clickCallback ) {
      var button = buttons[ buttonName ];

      if ( !button ) {
        button = document.createElement( "button" );
        button.appendChild( document.createTextNode( buttonText ) );
        button.addEventListener( "click", function() {
          clickCallback();
        }, false );
        buttons[ buttonName ] = button;
        options._container.appendChild( button );
      }
      return button;
    }

    // end = end || start + 0.5,
    // UL > LI elements?
    var popcorn = this,
        questions = options.questions || [],
        // allowPause = options.allowPause === false ? false : true,
        allowPause = options.allowPause = typeof allowPause === "undefined" ? true : !!options.allowPause,
        continueButtonText = options.continueButtonText || "Continue",
        previousButtonText = options.previousButtonText || "Previous",
        quiz = new Quiz(),
        selectedAnswers = {},
        buttons = {},
        correctAnswers = 0,
        quizNumber = QUIZ_NUMBER++,
        questionable = 0,
        quizComplete = false,
        paginated;

    for ( var idx = 0; idx < questions.length; idx++ ) {
      ( questions[ idx ].correctAnswer !== NO_ANSWER ) && questionable++;
    }

    return {
      _setup: function( options ) {
        var target = document.getElementById( options.target ),
            title = document.createElement( "div" );

        title.classList.add( "quiz-title" );
        paginated = options.paginate = typeof options.paginate === "undefined" ? true : !!options.paginate;

        options.decorators = options.decorators || [];
        options.title = options.title || "Quiz";

        title.innerHTML = options.title;

        options._container = document.createElement( "div" );
        // options._container.style.display = "none";

        // FIXME: make this options._container?
        options._parentContainer = document.createElement( "div" );
        options._parentContainer.style.display = "none";
        options._parentContainer.classList.add( "quiz-parent-container" );
        options._parentContainer.appendChild( title );
        options._parentContainer.appendChild( options._container );

        for ( var idx = 0; idx < options.decorators.length; idx++ ) {
          var decoratorOptions = options.decorators[ idx ];
          quiz.decorate( decoratorOptions.when, decoratorOptions.what );
        }

        // paginated && quiz.decorate( "prepare", "paginate" );

        // quiz.decorate( "end", "results" );
        // TODO: IS THIS RIGHT? or pass in target?
        quiz.prepare( options._container );

        if ( !target && Popcorn.plugin.debug ) {
          throw new Error( "target container doesn't exist" );
        }

        // FIXME: This is a work around until a bug is fixed in Popcorn Maker
        // Bug is not filed yet, but it has to do with the default Ids in editor's being "Media Element" (not supposed to have spaces in Ids)
        if ( target === popcorn.media.parentNode.parentNode ) {
          options._container.classList.add( "quiz-question-container" );
        }

        // if ( !options.question || !options.answers && Popcorn.plugin.debug ) {
        //   throw new Error( "a quiz needs a question and possible answers" );
        // }
        target && target.appendChild( options._parentContainer );
      },
      start: function( event, options ) {
        function toggleButtons() {
          var previousButton = addButton( "previousButton", previousButtonText, function() {
                quiz.previous();
                previousButton.style.display = quiz.hasPrevious() ? "inline" : "none";
              }),
              continueButton = addButton( "continueButton", continueButtonText, function() {
                if ( quiz.hasNext() ) {
                  quiz.next();
                  previousButton.style.display = "inline";
                }
                else {
                  quiz.end( options._container );
                  continueButton.style.display = "none";
                  previousButton.style.display = "none";
                }
              });

          continueButton.style.display = "inline";
          previousButton.style.display = "none";
        }

        if ( allowPause ) {
          popcorn.pause();
        }

        options._parentContainer.style.display = "inline";
        if ( !quizComplete ) {
          quiz.start();
          toggleButtons();
        }
      },
      end: function( event, options ) {
        options._parentContainer.style.display = "none";
      },
      _teardown: function( options ) {
        document.getElementById( options.target ) && document.getElementById( options.target ).removeChild( options._parentContainer );
      }
    };
  },
  {
    about: {
      name: "Popcorn Quiz Plugin",
      version: "0.1",
      author: "Brian Chirls, @bchirls",
      website: "http://github.com/brianchirls"
    },
    options: {
      questions: {
        elem: "input",
        type: "textarea",
        label: "Question"
      },
      paginate: {
        elem: "input",
        type: "checkbox",
        label: "Paginate",
        "default": true
      },
      decorators: {
        elem: "input",
        type: "textarea",
        label: "Show Results"
      },
      target: "questions",
      start: {
        elem: "input",
        type: "number",
        label: "Start Time"
      },
      end: {
        elem: "input",
        type: "number",
        label: "End Time"
      },
      title: {
        elem: "input",
        type: "textarea",
        label: "Title"
      }
    }
  });
})( Popcorn );
<|endoftext|>


<commit_before>define([
  'jquery',
  'lodash',
  'foundation',
  'module',
], function( $, _, foundation, module ) {
  var Modal = function( el ) {
    this.el = el;
    this.$el = $(el);
  };
  _.extend( Modal.prototype, {
    show: function() {
      window.$(this.el).reveal();
    },
    hide: function() {
      $(this.el).trigger( "reveal:close" );
    },
    isVisible: function() {
      return $(this.el).hasClass( "open" );
    }
  } );

  var modal = new Modal(module.config().modalId);
  return modal;
});<commit_msg>Removing bad comma<commit_after>define([
  'jquery',
  'lodash',
  'foundation',
  'module'
], function( $, _, foundation, module ) {
  var Modal = function( el ) {
    this.el = el;
    this.$el = $(el);
  };
  _.extend( Modal.prototype, {
    show: function() {
      window.$(this.el).reveal();
    },
    hide: function() {
      $(this.el).trigger( "reveal:close" );
    },
    isVisible: function() {
      return $(this.el).hasClass( "open" );
    }
  } );

  var modal = new Modal(module.config().modalId);
  return modal;
});<|endoftext|>


<commit_before>(function($){

    window.Edgar = window.Edgar || {};
    Edgar.YearSlider = function(options) {
        //options
        options = $.extend({
            containerElem: null,        // REQUIRED: the element that contains all the options
            loadingContainerElem: null, // REQUIRED: a sibling element to containerElem that contains a loading screen
            sliderElem: null,           // REQUIRED: empty div that will be converted into a slider
            scenarioElemName: null,     // REQUIRED: name of <radio> elements for selecting an emission scenario
            map: null,                  // REQUIRED: the open layers map object
            loadingBar: null,           // Will turn this empty div into a progress bar for loading
            yearLabelElem: null,        // Will change the inner text of this elem when the year changes
            defaultYear: 2015,
        }, options);

        this.MIN_YEAR = 2015;
        this.MAX_YEAR = 2085;

        //member vars (private)
        this._year = parseInt(options.defaultYear);
        this._layersByYear = {};
        this._map = options.map;
        this._speciesId = null;
        this._$container = $(options.containerElem);
        this._$loadingContainer = $(options.loadingContainerElem);
        this._$slider = $(options.sliderElem);
        this._$scenarios = $('input[name=' + options.scenarioElemName + ']');
        this._$loadingBar = (options.loadingBar === null ? null : $(options.loadingBar));
        this._$yearLabel = (options.yearLabelElem === null ? null : $(options.yearLabelElem));
        var self = this;

        //initial setup
        $(document).ready(function(){
            //init slider
            self._$slider.slider({
                min: self.MIN_YEAR,
                max: self.MAX_YEAR,
                value: self._year,
                step: 10,
                change: function(event, ui){
                    self.setYear(ui.value);
                },
                slide: function(event, ui){
                    self.setYear(ui.value);
                }
            });

            //init loading bar
            if(self._$loadingBar) self._$loadingBar.progressbar({value:0});

            //init year label
            if(self._$yearLabel) {
                var year = self._year;
                self._year = undefined;
                self.setYear(year);
            }

            //init scenarios
            self._$scenarios.change(function(){
                setTimeout(function(){
                    self.setScenario();
                }, 1);
            });
       });

        this.setYear = function(year){
            year = parseInt(year);
            if(this._year == year) return;

            this._year = year;
            this._$slider.slider("value", this._year);

            if(this._year > 2050){
                //TODO: change site logo to cyborg raven
            }

            if(this._$yearLabel){
                this._$yearLabel.text(''+this._year);

                var $thumb = this._$slider.slider("widget").find('a');
                var left = $thumb.position().left + this._$slider.position().left - ($thumb.width() / 2) - this._$yearLabel.width();
                this._$yearLabel.css('left', left + "px");
            }

            this._setLayerOpacities();
        }

        /*!
        *  Disable by setting species id to null
        */
        this.setSpeciesId = function(speciesId){
            if(speciesId !== null)
                speciesId = parseInt(speciesId);

            if(this._speciesId === speciesId) return;

            this._speciesId = speciesId;
            this._reloadLayers();
        }

        this.setScenario = function(){
            scenario = '' + self._$scenarios.filter(':checked').val();
            if(this._scenario == scenario) return;

            this._scenario = scenario;
            this._reloadLayers();
        }

        this.playAnimation = function(){
            var self = this;

            // switch the step to 1 to get smooth transitions between years
            self._$slider.slider('option', 'step', 1);

            var dummyElem = $('

').css('height', this.MIN_YEAR);
            dummyElem.animate({height: this.MAX_YEAR}, {
                duration: 5000,
                easing: 'linear',
                step: function(year){
                    self.setYear(year);
                },
                complete: function() {
                    self._$slider.slider('option', 'step', 10);
                }
            });
        }

        this._reloadLayers = function(){
            this._removeAllLayers();

            if(this._speciesId){
                this._beginLoading();
                this._addLayerForYear(2015);
                this._addLayerForYear(2025);
                this._addLayerForYear(2035);
                this._addLayerForYear(2045);
                this._addLayerForYear(2055);
                this._addLayerForYear(2065);
                this._addLayerForYear(2075);
            }
        }

        this._addLayerForYear = function(year){
            var mapPath = Edgar.util.mappath(this._speciesId, year, this._scenario);

            var layer = new OpenLayers.Layer.WMS(
                "Climate Suitability in " + year,
                mapToolBaseUrl + 'wms_with_auto_determined_threshold.php',
                {
                    MODE: 'map',
                    MAP: 'edgar_master.map',
                    DATA: mapPath,
                    SPECIESID: this._speciesId,
                    REASPECT: 'true',
                    TRANSPARENT: 'true'
                },
                {
                    isBaseLayer: false,
                    transitionEffect: 'resize',
                    displayInLayerSwitcher: false,
                    singleTile: true,
                    ratio: 1.5,
                }
            );

            layer.events.register('loadend', this, this._layerDidLoad);
            layer.setOpacity(0);

            this._layersByYear[year] = layer;
            this._map.addLayer(layer);
        }

        this._removeAllLayers = function(){
            var self = this;
            $.each(this._layersByYear, function(year, layer){
                layer.events.unregister('loadend', self, self._layerDidLoad);
                self._map.removeLayer(layer);
            });
            this._layersByYear = {};
        }

        this._layerDidLoad = function(event){
            var numLoaded = 0;
            var numTotal = 0;
            $.each(this._layersByYear, function(year, layer){
                numTotal += 1;
                if(layer.numLoadingTiles == 0) numLoaded += 1;
            });

            this._updateLoading(numLoaded/numTotal);

            if(numLoaded == numTotal)
                this._endLoading();
        }

        this._beginLoading = function(){
            this._updateLoading(0.05);

            var self = this;
            this._$container.stop().slideUp('fast', function(){
                self._$loadingContainer.stop().slideDown('fast');
            });
        }

        this._updateLoading = function(percentDone){
            this._$loadingBar.progressbar('value', percentDone*100);
        }

        this._endLoading = function(){
            this._setLayerOpacities();
            this.setYear(options.defaultYear);

            var self = this;
            this._$loadingContainer.stop().slideUp('fast', function(){
                self._$container.stop().slideDown('fast').effect('hilight', 'slow');
            });
        }

        this._setLayerOpacities = function(){
            if(!this._speciesId) return;

            var currentYear = this._year;
            var sortedYears = $.map(this._layersByYear, function(layer, year){ return parseInt(year); });
            sortedYears.sort();

            //find two closest years to interpolate between
            var lowerYear = sortedYears[0];
            var upperYear = null;
            $.each(sortedYears, function(idx, year){
                if(year >= currentYear){
                    upperYear = year;
                    return false; //stops iteration
                } else {
                    lowerYear = year;
                }
            });

            //if exactly on a year, no interpolation necessary
            if(upperYear == currentYear){
                $.each(this._layersByYear, function(year, layer){
                    layer.setOpacity((year == currentYear ? 1 : 0));
                });
            } else {
                //else, interpolate
                $.each(this._layersByYear, function(year, layer){
                    var interp = (currentYear - lowerYear) / (upperYear - lowerYear);
                    var opacity = 0;
                    if(year == upperYear){
                        opacity = interp;
                    } else if(year == lowerYear){
                        opacity = 1 - interp;
                    }
                    layer.setOpacity(opacity);
                });
            }
        }
    };

})(jQuery);
<commit_msg>Reload future layers on pan and zoom<commit_after>(function($){

    window.Edgar = window.Edgar || {};
    Edgar.YearSlider = function(options) {
        //options
        options = $.extend({
            containerElem: null,        // REQUIRED: the element that contains all the options
            loadingContainerElem: null, // REQUIRED: a sibling element to containerElem that contains a loading screen
            sliderElem: null,           // REQUIRED: empty div that will be converted into a slider
            scenarioElemName: null,     // REQUIRED: name of <radio> elements for selecting an emission scenario
            map: null,                  // REQUIRED: the open layers map object
            loadingBar: null,           // Will turn this empty div into a progress bar for loading
            yearLabelElem: null,        // Will change the inner text of this elem when the year changes
            defaultYear: 2015,
        }, options);

        this.MIN_YEAR = 2015;
        this.MAX_YEAR = 2085;

        //member vars (private)
        this._year = parseInt(options.defaultYear);
        this._layersByYear = {};
        this._map = options.map;
        this._speciesId = null;
        this._$container = $(options.containerElem);
        this._$loadingContainer = $(options.loadingContainerElem);
        this._$slider = $(options.sliderElem);
        this._$scenarios = $('input[name=' + options.scenarioElemName + ']');
        this._$loadingBar = (options.loadingBar === null ? null : $(options.loadingBar));
        this._$yearLabel = (options.yearLabelElem === null ? null : $(options.yearLabelElem));
        this._numReloadSuspensions = 0;
        this._needsReload = false;
        var self = this;

        //initial setup (runs once upon object creation)
        this._init = function(){
            self._map.events.register('movestart', this, function(){
                self._suspendReloadOfLayers();
            });
            self._map.events.register('moveend', this, function(){
                //reload layers after a delay to stop uneccessary requests to the map server
                setTimeout(function(){
                    self._resumeReloadOfLayers();
                    self._reloadLayers();
                }, 2000);
            });

            //init slider
            self._$slider.slider({
                min: self.MIN_YEAR,
                max: self.MAX_YEAR,
                value: self._year,
                step: 10,
                change: function(event, ui){
                    self.setYear(ui.value);
                },
                slide: function(event, ui){
                    self.setYear(ui.value);
                }
            });

            //init loading bar
            if(self._$loadingBar) self._$loadingBar.progressbar({value:0});

            //init year label
            if(self._$yearLabel) {
                var year = self._year;
                self._year = undefined;
                self.setYear(year);
            }

            //init scenarios
            self._$scenarios.change(function(){
                setTimeout(function(){
                    self.setScenario();
                }, 1);
            });
        };

        this.setYear = function(year){
            year = parseInt(year);
            if(this._year == year) return;

            this._year = year;
            this._$slider.slider("value", this._year);

            if(this._year > 2050){
                //TODO: change site logo to cyborg raven
            }

            if(this._$yearLabel){
                this._$yearLabel.text(''+this._year);

                var $thumb = this._$slider.slider("widget").find('a');
                var left = $thumb.position().left + this._$slider.position().left - ($thumb.width() / 2) - this._$yearLabel.width();
                this._$yearLabel.css('left', left + "px");
            }

            this._setLayerOpacities();
        }

        /*!
        *  Disable by setting species id to null
        */
        this.setSpeciesId = function(speciesId){
            if(speciesId !== null)
                speciesId = parseInt(speciesId);

            if(this._speciesId === speciesId) return;

            this._speciesId = speciesId;
            this._reloadLayers();
        }

        this.setScenario = function(){
            scenario = '' + self._$scenarios.filter(':checked').val();
            if(this._scenario == scenario) return;

            this._scenario = scenario;
            this._reloadLayers();
        }

        this.playAnimation = function(){
            var self = this;

            // switch the step to 1 to get smooth transitions between years
            self._$slider.slider('option', 'step', 1);

            var dummyElem = $('

').css('height', this.MIN_YEAR);
            dummyElem.animate({height: this.MAX_YEAR}, {
                duration: 5000,
                easing: 'linear',
                step: function(year){
                    self.setYear(year);
                },
                complete: function() {
                    self._$slider.slider('option', 'step', 10);
                }
            });
        }

        this._reloadLayers = function(){
            //check if reload is suspended
            if(this._numReloadSuspensions > 0){
                this._needsReload = true;
                console.log('Blocked! ' + this._numReloadSuspensions);
                return;
            } else {
                this._needsReload = false;
                console.log('GOGOGOG');
            }

            this._removeAllLayers();

            if(this._speciesId){
                this._beginLoading();
                this._addLayerForYear(2015);
                this._addLayerForYear(2025);
                this._addLayerForYear(2035);
                this._addLayerForYear(2045);
                this._addLayerForYear(2055);
                this._addLayerForYear(2065);
                this._addLayerForYear(2075);
            }
        }

        this._suspendReloadOfLayers = function(){
            this._numReloadSuspensions += 1;
        }

        this._resumeReloadOfLayers = function(){
            this._numReloadSuspensions -= 1;
        }

        this._addLayerForYear = function(year){
            var mapPath = Edgar.util.mappath(this._speciesId, year, this._scenario);

            var layer = new OpenLayers.Layer.WMS(
                "Climate Suitability in " + year,
                mapToolBaseUrl + 'wms_with_auto_determined_threshold.php',
                {
                    MODE: 'map',
                    MAP: 'edgar_master.map',
                    DATA: mapPath,
                    SPECIESID: this._speciesId,
                    REASPECT: 'true',
                    TRANSPARENT: 'true'
                },
                {
                    isBaseLayer: false,
                    transitionEffect: 'resize',
                    displayInLayerSwitcher: false,
                    singleTile: true,
                    ratio: 1.5,
                }
            );

            layer.events.register('loadend', this, this._layerDidLoad);
            layer.setOpacity(0);

            this._layersByYear[year] = layer;
            this._map.addLayer(layer);
        }

        this._removeAllLayers = function(){
            var self = this;
            $.each(this._layersByYear, function(year, layer){
                layer.events.unregister('loadend', self, self._layerDidLoad);
                self._map.removeLayer(layer);
            });
            this._layersByYear = {};
        }

        this._layerDidLoad = function(event){
            var numLoaded = 0;
            var numTotal = 0;
            $.each(this._layersByYear, function(year, layer){
                numTotal += 1;
                if(layer.numLoadingTiles == 0) numLoaded += 1;
            });

            this._updateLoading(numLoaded/numTotal);

            if(numLoaded == numTotal)
                this._endLoading();
        }

        this._beginLoading = function(){
            this._updateLoading(0.05);

            var self = this;
            this._$container.stop().slideUp('fast', function(){
                self._$loadingContainer.stop().slideDown('fast');
            });
        }

        this._updateLoading = function(percentDone){
            this._$loadingBar.progressbar('value', percentDone*100);
        }

        this._endLoading = function(){
            this._setLayerOpacities();
            this.setYear(options.defaultYear);

            var self = this;
            this._$loadingContainer.stop().slideUp('fast', function(){
                self._$container.stop().slideDown('fast').effect('hilight', 'slow');
            });
        }

        this._setLayerOpacities = function(){
            if(!this._speciesId) return;

            var currentYear = this._year;
            var sortedYears = $.map(this._layersByYear, function(layer, year){ return parseInt(year); });
            sortedYears.sort();

            //find two closest years to interpolate between
            var lowerYear = sortedYears[0];
            var upperYear = null;
            $.each(sortedYears, function(idx, year){
                if(year >= currentYear){
                    upperYear = year;
                    return false; //stops iteration
                } else {
                    lowerYear = year;
                }
            });

            //if exactly on a year, no interpolation necessary
            if(upperYear == currentYear){
                $.each(this._layersByYear, function(year, layer){
                    layer.setOpacity((year == currentYear ? 1 : 0));
                });
            } else {
                //else, interpolate
                $.each(this._layersByYear, function(year, layer){
                    var interp = (currentYear - lowerYear) / (upperYear - lowerYear);
                    var opacity = 0;
                    if(year == upperYear){
                        opacity = interp;
                    } else if(year == lowerYear){
                        opacity = 1 - interp;
                    }
                    layer.setOpacity(opacity);
                });
            }
        }

        //do init last
        $(document).ready(function(){ self._init(); })
    };

})(jQuery);
<|endoftext|>


<commit_before>/*
 Copyright 2017 Aleksandar Jankovic

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

require('../library/polyfills');

const socketManager = require('../library/SocketManager');
const storageManager = require('../library/StorageManager');
const eventCentral = require('../library/EventCentral');
const OnlineStatus = require('../library/view/templates/OnlineStatus');

const mainView = document.getElementById('main');
const usersView = document.getElementById('users');
const teamsView = document.getElementById('teams');
const roomsView = document.getElementById('rooms');
const messagesView = document.getElementById('messages');

const usersList = document.getElementById('listUsers');
const teamsList = document.getElementById('listTeams');
const roomsList = document.getElementById('listRooms');
const messagesList = document.getElementById('listMessages');

[usersView, teamsView, roomsView, messagesView].forEach((view) => {
  const top = view.firstElementChild;

  top.addEventListener('click', () => {
    view.lastElementChild.classList.toggle('hide');
  });
});

/**
 * Reset all lists
 */
function resetView() {
  usersList.innerHTML = '';
  teamsList.innerHTML = '';
  roomsList.innerHTML = '';
  messagesList.innerHTML = '';

  usersList.classList.add('hide');
  teamsList.classList.add('hide');
  roomsList.classList.add('hide');
  messagesList.classList.add('hide');
}

/**
 * Create user row
 * @param {Object} params.user User
 * @returns {Element} List item
 */
function createUserRow({ user }) {
  const listItem = document.createElement('LI');
  const userSpan = document.createElement('SPAN');
  const fullSpan = document.createElement('SPAN');
  const banButton = document.createElement('BUTTON');
  const verifyButton = document.createElement('BUTTON');
  const buttonContainer = document.createElement('DIV');

  buttonContainer.classList.add('buttonContainer');

  userSpan.appendChild(document.createTextNode(user.userName));
  fullSpan.appendChild(document.createTextNode(user.fullName));

  buttonContainer.appendChild(banButton);

  if (!user.verified) {
    verifyButton.appendChild(document.createTextNode('Verify'));
    buttonContainer.appendChild(verifyButton);

    if (!user.banned) {
      listItem.classList.add('unverified');
    }
  }

  if (user.banned) {
    banButton.appendChild(document.createTextNode('Unban'));
    listItem.classList.add('banned');
  } else {
    banButton.appendChild(document.createTextNode('Ban'));
  }

  banButton.addEventListener('click', () => {
    if (user.banned) {
      socketManager.emitEvent('ban', { user, shouldBan: false }, (banData) => {
        if (banData.error) {
          console.log('Unban user', banData.error);

          return;
        }

        user.banned = false;
        listItem.classList.remove('banned');
        banButton.innerText = 'Ban';
      });
    } else {
      socketManager.emitEvent('ban', { user }, (banData) => {
        if (banData.error) {
          console.log('Unban user', banData.error);

          return;
        }

        user.banned = true;
        listItem.classList.add('banned');
        banButton.innerText = 'Unban';
      });
    }
  });
  verifyButton.addEventListener('click', () => {
    if (!user.verified) {
      socketManager.emitEvent('verifyUser', { user }, (verifyData) => {
        if (verifyData.error) {
          console.log('Verify user', verifyData.error);

          return;
        }

        verifyButton.remove();
        listItem.classList.remove('unverified');
      });
    }
  });

  listItem.appendChild(userSpan);

  if (user.fullName !== user.userName) {
    listItem.appendChild(fullSpan);
  }

  listItem.appendChild(buttonContainer);

  return listItem;
}

/**
 * Populate all lists
 */
function populateAll() {
  socketManager.emitEvent('listUsers', { includeInactive: true }, (usersData) => {
    if (usersData.error) {
      console.log('listUsers error', usersData.error);

      return;
    }

    socketManager.emitEvent('listAliases', { includeInactive: true }, (aliasesData) => {
      if (aliasesData.error) {
        console.log('listAliases error', aliasesData.error);

        return;
      }

      const { onlineUsers, offlineUsers } = usersData.data;
      const { aliases } = aliasesData.data;
      const allUsers = onlineUsers.concat(offlineUsers).filter(user => aliases.indexOf(user.userName) === -1).sort();

      const fragment = document.createDocumentFragment();

      usersList.classList.add('hide');
      usersList.innerHTML = '';

      allUsers.map(user => createUserRow({ user })).forEach(row => fragment.appendChild(row));

      usersList.appendChild(fragment);
    });
  });

  socketManager.emitEvent('listRooms', {}, (roomsData) => {
    if (roomsData.error) {
      console.log('listRooms error', roomsData.error);

      return;
    }

    const { rooms = [], followedRooms = [], ownedRooms = [], protectedRooms = [] } = roomsData.data;
    const allRooms = rooms.concat(followedRooms, ownedRooms, protectedRooms);

    const fragment = document.createDocumentFragment();

    roomsList.classList.add('hide');
    roomsList.innerHTML = '';

    allRooms.map(room => createRoomRow({ room })).forEach(row => fragment.appendChild(row));

    roomsList.appendChild(fragment);
  });
}

/**
 * Create room row
 * @param {Object} params.room Room
 * @returns {Element} List item
 */
function createRoomRow({ room }) {
  const listItem = document.createElement('LI');
  const roomNameSpan = document.createElement('SPAN');

  roomNameSpan.appendChild(document.createTextNode(room.roomName));
  listItem.appendChild(roomNameSpan);

  return listItem;
}

/**
 * Create interface
 */
function start() {
  if (storageManager.getAccessLevel() < 9) {
    mainView.appendChild(document.createTextNode('You are not an admin'));

    return;
  }

  const onlineStatus = new OnlineStatus(document.getElementById('onlineStatus'));

  socketManager.addEvents([
    {
      event: 'disconnect',
      func: () => {
        onlineStatus.setOffline();
        resetView();
      },
    }, {
      event: 'reconnect',
      func: () => {
        onlineStatus.setOnline();
        socketManager.reconnectDone();
        populateAll();
      },
    }, {
      event: 'startup',
      func: () => {
        onlineStatus.setOnline();
        populateAll();
      },
    }, {
      event: 'room',
      func: ({ room, isProtected }) => {
        eventCentral.triggerEvent({
          event: eventCentral.Events.NEWROOM,
          params: { room, isProtected },
        });
      },
    }, {
      event: 'team',
      func: ({ team }) => {
        eventCentral.triggerEvent({
          event: eventCentral.Events.NEWTEAM,
          params: { team },
        });
      },
    },
  ]);

  eventCentral.addWatcher({
    watcherParent: usersView,
    event: eventCentral.Events.LOGOUT,
    func: () => {
      storageManager.removeUser();
      eventCentral.triggerEvent({ event: eventCentral.Events.USER, params: { changedUser: true } });
    },
  });

  eventCentral.addWatcher({
    watcherParent: this,
    event: eventCentral.Events.USER,
    func: ({ changedUser }) => {
      if (changedUser) {
        resetView();
      }

      populateAll();
    },
  });
}

start();
<commit_msg>eslint fix<commit_after>/*
 Copyright 2017 Aleksandar Jankovic

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

require('../library/polyfills');

const socketManager = require('../library/SocketManager');
const storageManager = require('../library/StorageManager');
const eventCentral = require('../library/EventCentral');
const OnlineStatus = require('../library/view/templates/OnlineStatus');

const mainView = document.getElementById('main');
const usersView = document.getElementById('users');
const teamsView = document.getElementById('teams');
const roomsView = document.getElementById('rooms');
const messagesView = document.getElementById('messages');

const usersList = document.getElementById('listUsers');
const teamsList = document.getElementById('listTeams');
const roomsList = document.getElementById('listRooms');
const messagesList = document.getElementById('listMessages');

[usersView, teamsView, roomsView, messagesView].forEach((view) => {
  const top = view.firstElementChild;

  top.addEventListener('click', () => {
    view.lastElementChild.classList.toggle('hide');
  });
});

/**
 * Reset all lists
 */
function resetView() {
  usersList.innerHTML = '';
  teamsList.innerHTML = '';
  roomsList.innerHTML = '';
  messagesList.innerHTML = '';

  usersList.classList.add('hide');
  teamsList.classList.add('hide');
  roomsList.classList.add('hide');
  messagesList.classList.add('hide');
}

/**
 * Create user row
 * @param {Object} params.user User
 * @returns {Element} List item
 */
function createUserRow({ user }) {
  const listItem = document.createElement('LI');
  const userSpan = document.createElement('SPAN');
  const fullSpan = document.createElement('SPAN');
  const banButton = document.createElement('BUTTON');
  const verifyButton = document.createElement('BUTTON');
  const buttonContainer = document.createElement('DIV');

  buttonContainer.classList.add('buttonContainer');

  userSpan.appendChild(document.createTextNode(user.userName));
  fullSpan.appendChild(document.createTextNode(user.fullName));

  buttonContainer.appendChild(banButton);

  if (!user.verified) {
    verifyButton.appendChild(document.createTextNode('Verify'));
    buttonContainer.appendChild(verifyButton);

    if (!user.banned) {
      listItem.classList.add('unverified');
    }
  }

  if (user.banned) {
    banButton.appendChild(document.createTextNode('Unban'));
    listItem.classList.add('banned');
  } else {
    banButton.appendChild(document.createTextNode('Ban'));
  }

  banButton.addEventListener('click', () => {
    if (user.banned) {
      socketManager.emitEvent('ban', { user, shouldBan: false }, (banData) => {
        if (banData.error) {
          console.log('Unban user', banData.error);

          return;
        }

        user.banned = false;
        listItem.classList.remove('banned');
        banButton.innerText = 'Ban';
      });
    } else {
      socketManager.emitEvent('ban', { user }, (banData) => {
        if (banData.error) {
          console.log('Unban user', banData.error);

          return;
        }

        user.banned = true;
        listItem.classList.add('banned');
        banButton.innerText = 'Unban';
      });
    }
  });
  verifyButton.addEventListener('click', () => {
    if (!user.verified) {
      socketManager.emitEvent('verifyUser', { user }, (verifyData) => {
        if (verifyData.error) {
          console.log('Verify user', verifyData.error);

          return;
        }

        verifyButton.remove();
        listItem.classList.remove('unverified');
      });
    }
  });

  listItem.appendChild(userSpan);

  if (user.fullName !== user.userName) {
    listItem.appendChild(fullSpan);
  }

  listItem.appendChild(buttonContainer);

  return listItem;
}

/**
 * Create room row
 * @param {Object} params.room Room
 * @returns {Element} List item
 */
function createRoomRow({ room }) {
  const listItem = document.createElement('LI');
  const roomNameSpan = document.createElement('SPAN');

  roomNameSpan.appendChild(document.createTextNode(room.roomName));
  listItem.appendChild(roomNameSpan);

  return listItem;
}

/**
 * Populate all lists
 */
function populateAll() {
  socketManager.emitEvent('listUsers', { includeInactive: true }, (usersData) => {
    if (usersData.error) {
      console.log('listUsers error', usersData.error);

      return;
    }

    socketManager.emitEvent('listAliases', { includeInactive: true }, (aliasesData) => {
      if (aliasesData.error) {
        console.log('listAliases error', aliasesData.error);

        return;
      }

      const { onlineUsers, offlineUsers } = usersData.data;
      const { aliases } = aliasesData.data;
      const allUsers = onlineUsers.concat(offlineUsers).filter(user => aliases.indexOf(user.userName) === -1).sort();

      const fragment = document.createDocumentFragment();

      usersList.classList.add('hide');
      usersList.innerHTML = '';

      allUsers.map(user => createUserRow({ user })).forEach(row => fragment.appendChild(row));

      usersList.appendChild(fragment);
    });
  });

  socketManager.emitEvent('listRooms', {}, (roomsData) => {
    if (roomsData.error) {
      console.log('listRooms error', roomsData.error);

      return;
    }

    const { rooms = [], followedRooms = [], ownedRooms = [], protectedRooms = [] } = roomsData.data;
    const allRooms = rooms.concat(followedRooms, ownedRooms, protectedRooms);

    const fragment = document.createDocumentFragment();

    roomsList.classList.add('hide');
    roomsList.innerHTML = '';

    allRooms.map(room => createRoomRow({ room })).forEach(row => fragment.appendChild(row));

    roomsList.appendChild(fragment);
  });
}

/**
 * Create interface
 */
function start() {
  if (storageManager.getAccessLevel() < 9) {
    mainView.appendChild(document.createTextNode('You are not an admin'));

    return;
  }

  const onlineStatus = new OnlineStatus(document.getElementById('onlineStatus'));

  socketManager.addEvents([
    {
      event: 'disconnect',
      func: () => {
        onlineStatus.setOffline();
        resetView();
      },
    }, {
      event: 'reconnect',
      func: () => {
        onlineStatus.setOnline();
        socketManager.reconnectDone();
        populateAll();
      },
    }, {
      event: 'startup',
      func: () => {
        onlineStatus.setOnline();
        populateAll();
      },
    }, {
      event: 'room',
      func: ({ room, isProtected }) => {
        eventCentral.triggerEvent({
          event: eventCentral.Events.NEWROOM,
          params: { room, isProtected },
        });
      },
    }, {
      event: 'team',
      func: ({ team }) => {
        eventCentral.triggerEvent({
          event: eventCentral.Events.NEWTEAM,
          params: { team },
        });
      },
    },
  ]);

  eventCentral.addWatcher({
    watcherParent: usersView,
    event: eventCentral.Events.LOGOUT,
    func: () => {
      storageManager.removeUser();
      eventCentral.triggerEvent({ event: eventCentral.Events.USER, params: { changedUser: true } });
    },
  });

  eventCentral.addWatcher({
    watcherParent: this,
    event: eventCentral.Events.USER,
    func: ({ changedUser }) => {
      if (changedUser) {
        resetView();
      }

      populateAll();
    },
  });
}

start();
<|endoftext|>


<commit_before>//from http://ejohn.org/blog/simple-javascript-inheritance/
// Inspired by base2 and Prototype
(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  // The base Class implementation (does nothing)
  this.Class = function(){};

  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;

    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;

            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];

            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);
            this._super = tmp;

            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }

    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }

    // Populate our constructed prototype object
    Class.prototype = prototype;

    // Enforce the constructor to be what we expect
    Class.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;

    return Class;
  };
})();

//considered for removal in HTML5
var TimeRanges = Class.extend({
    //readonly
    length: 0,
    start: function(index) {
      return this.starts[index];
    },
    end: function(index) {
      return this.ends[index];
    },
    //private
    starts: [],
    ends: [],
    add: function(start, end) {
      this.starts.push(start);
      this.ends.push(end);
      this.length++;
    }
});

var HTMLMediaElement = Class.extend({
  //network state
  NETWORK_EMPTY: 0,
  NETWORK_IDLE: 1,
  NETWORK_LOADING: 2,
  NETWORK_LOADED: 3,
  NETWORK_NO_SOURCE: 4,

  //ready state
  HAVE_NOTHING: 0,
  HAVE_METADATA: 1,
  HAVE_CURRENT_DATA: 2,
  HAVE_FUTURE_DATA: 3,
  HAVE_ENOUGH_DATA: 4,

  // error state
  //readonly attribute MediaError error;
  error: 0,

  //attribute DOMString src;
  src: null,
  //readonly attribute currentSrc
  currentSrc: null,
  //readonly attribute unsigned short networkState
  networkState: this.NETWORK_EMPTY,
  //attribute boolean autobuffer
  autobuffer: false,
  //readonly attribute TimeRanges buffered
  buffered: null,
  //readonly attribute
  readyState: this.HAVE_NOTHING,
  seeking: false,

  //playback state (floats)
  currentTime: 0.0,
  //readonly
  startTime: 0.0,
  //readonly
  duration: 0.0,
  paused: 0.0,
  defaultPlaybackRate: 1.0,
  playbackRate: 1.0,
  //TimeRanges - readonly
  played: new TimeRanges(),
  seekable: new TimeRanges(),
  ended: false,
  autoplay: false,
  loop: false,
  controls: false,
  volume: 0,
  muted: false,

  init: function(element) {
    this.domElement = element;
    this.src = this.domElement.getAttribute("src");
    this.autobuffer = (this.domElement.getAttribute("autobuffer")=="true");
    this.autoplay = (this.domElement.getAttribute("autoplay")=="true");
    this.loop = (this.domElement.getAttribute("loop")=="true");
    this.controls = (this.domElement.getAttribute("controls")=="true");
    this.load();
  },

  //returns void
  load: function() {
    this.currentSrc = this.src;
    //find the right source
    var candidate = this.domElement.firstChild;
    var maybe = null;
    //loop until we have found a type that works probably
    while(candidate!=null&&this.currentSrc==null) {
      if (candidate.nodeName=="SOURCE"||candidate.nodeName=="source") {
        if (candidate.getAttribute("src")!=null) {
          if (this.canPlayType(candidate.getAttribute("type"))=="probably") {
            this.currentSrc = candidate.getAttribute("src");
          } else if (maybe==null&&this.canPlayType(candidate.getAttribute("type"))=="maybe") {
            maybe = candidate.getAttribute("src");
          }
        }
      }
      candidate = candidate.nextSibling;
    }
    //fall back to the first type that works maybe
    if (this.currentSrc==null&&maybe!=null) {
      this.currentSrc = maybe;
    }
    //override this in concrete implementations to build the widget
  },

  //returns String
  canPlayType: function(type) {
    return "no";
  },

  play: function() {
    //TODO
    //purely abstract
  },

  pause: function() {
    //TODO
    //purely abstract
  },

  //returns void
  //className, id - String
  //start, end - float
  //pauseOnExit - boolean
  //enterCallback, exitCallback - function
  addCueRange: function(className, id, start, end, pauseOnExit, enterCallback, exitCallback) {
    if (!this.cueRanges[className]||this.cueRanges[className]==null) {
      this.cueRanges[className] = [];
    }

    var cueRange = {
      start: start,
      end: end,
      pauseOnExit: pauseOnExit,
      enterCallback: enterCallback,
      exitCallback: exitCallback,
    };
    this.cueRanges[className].push(cueRange);
  },

  removeCueRange: function(className) {
    //reset cue ranges
    cueRanges[className] = [];
  },

  //private
  cueRanges: {},
  lastPosition: 0.0,

  checkCueRanges(currentPosition) {
    var entering = [];
    var exititing = [];
    for (className in this.cueRanges) {
      if (this.cueRanges.hasOwnProperty(className)) {
        var cues = this.cueRanges[className];
        for (var i=0;i<cues.length;i++) {
          var cue = cues[i];
          if (currentPosition>this.lastPosition) {
            if (cue.start>this.lastPosition&&cue.start<currentPosition) {
              entering.push(cue);
            }
            if (cue.end>this.lastPosition&&cue.end<currentPosition) {
              exititing.push(cue);
            }
          } else if (currentPosition<this.lastPosition) {
            if (cue.end>lastPosition&&cue.end<currentPosition) {
              entering.push(cue);
            }
            if (cue.start>lastPosition&&cue.start<currentPosition) {
              exititing.push(cue);
            }
          }
        }
      }
    }
    //call the entering events
    for (var i=0;i<entering.length;i++) {
      if (entering[i].enterCallback) {
        entering[i].enterCallback.call(this, entering[i].id);
      }
    }
    //call the exiting events
    for (var i=0;i<exiting.length;i++) {
      if (exiting[i].exitCallback) {
        exiting[i].exitCallback.call(this, exiting[i].id);
      }
      if (exiting[i].pauseOnExit) {
        this.pause();
      }
    }
  }
});

var HTMLVideoElement = HTMLMediaElement.extend({
    width: "0px",
    height: "0px",
    videoWidth: 0,
    videoHeight: 0,
    //poster frame
    poster: null,

    init: function(element) {
      this._super(element);
    }
});

var HTMLAudioElement = HTMLMediaElement.extend({
    //no additional properties
    init: function(element) {
      this._super(element);
    },

    canPlayType: function(type) {
      if (type.match(/^audio\/mp3/)) {
        return "probably";
      }
      if (type.match(/^audio\/wav/)) {
        return "probably";
      }
      if (type.match(/^audio\/(x-)?aiff/)) {
        return "probably";
      }
      if (type.match(/^audio\//)) {
        return "maybe";
      }
      return "no";
    }
});

function getHTML5Tags() {
  var audios = document.getElementsByTagName("audio");
  var videos = document.getElementsByTagName("video");
  var html5tags = [];
  for (var i=0;i<audios.length;i++) {
    html5tags.push(new HTMLAudioElement(audios[i]));
  }

  for (var i=0;i<videos.length;i++) {
    html5tags.push(new HTMLVideoElement(audios[i]));
  }
  return html5tags;
}
<commit_msg>basic video attribute support<commit_after>//from http://ejohn.org/blog/simple-javascript-inheritance/
// Inspired by base2 and Prototype
(function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  // The base Class implementation (does nothing)
  this.Class = function(){};

  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;

    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;

            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];

            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);
            this._super = tmp;

            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }

    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }

    // Populate our constructed prototype object
    Class.prototype = prototype;

    // Enforce the constructor to be what we expect
    Class.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;

    return Class;
  };
})();

//considered for removal in HTML5
var TimeRanges = Class.extend({
    //readonly
    length: 0,
    start: function(index) {
      return this.starts[index];
    },
    end: function(index) {
      return this.ends[index];
    },
    //private
    starts: [],
    ends: [],
    add: function(start, end) {
      this.starts.push(start);
      this.ends.push(end);
      this.length++;
    }
});

var HTMLMediaElement = Class.extend({
  //network state
  NETWORK_EMPTY: 0,
  NETWORK_IDLE: 1,
  NETWORK_LOADING: 2,
  NETWORK_LOADED: 3,
  NETWORK_NO_SOURCE: 4,

  //ready state
  HAVE_NOTHING: 0,
  HAVE_METADATA: 1,
  HAVE_CURRENT_DATA: 2,
  HAVE_FUTURE_DATA: 3,
  HAVE_ENOUGH_DATA: 4,

  // error state
  //readonly attribute MediaError error;
  error: 0,

  //attribute DOMString src;
  src: null,
  //readonly attribute currentSrc
  currentSrc: null,
  //readonly attribute unsigned short networkState
  networkState: this.NETWORK_EMPTY,
  //attribute boolean autobuffer
  autobuffer: false,
  //readonly attribute TimeRanges buffered
  buffered: null,
  //readonly attribute
  readyState: this.HAVE_NOTHING,
  seeking: false,

  //playback state (floats)
  currentTime: 0.0,
  //readonly
  startTime: 0.0,
  //readonly
  duration: 0.0,
  paused: 0.0,
  defaultPlaybackRate: 1.0,
  playbackRate: 1.0,
  //TimeRanges - readonly
  played: new TimeRanges(),
  seekable: new TimeRanges(),
  ended: false,
  autoplay: false,
  loop: false,
  controls: false,
  volume: 0,
  muted: false,

  init: function(element) {
    this.domElement = element;
    this.src = this.domElement.getAttribute("src");
    this.autobuffer = (this.domElement.getAttribute("autobuffer")=="true");
    this.autoplay = (this.domElement.getAttribute("autoplay")=="true");
    this.loop = (this.domElement.getAttribute("loop")=="true");
    this.controls = (this.domElement.getAttribute("controls")=="true");
    this.load();
  },

  //returns void
  load: function() {
    this.currentSrc = this.src;
    //find the right source
    var candidate = this.domElement.firstChild;
    var maybe = null;
    //loop until we have found a type that works probably
    while(candidate!=null&&this.currentSrc==null) {
      if (candidate.nodeName=="SOURCE"||candidate.nodeName=="source") {
        if (candidate.getAttribute("src")!=null) {
          if (this.canPlayType(candidate.getAttribute("type"))=="probably") {
            this.currentSrc = candidate.getAttribute("src");
          } else if (maybe==null&&this.canPlayType(candidate.getAttribute("type"))=="maybe") {
            maybe = candidate.getAttribute("src");
          }
        }
      }
      candidate = candidate.nextSibling;
    }
    //fall back to the first type that works maybe
    if (this.currentSrc==null&&maybe!=null) {
      this.currentSrc = maybe;
    }
    //override this in concrete implementations to build the widget
  },

  //returns String
  canPlayType: function(type) {
    return "no";
  },

  play: function() {
    //TODO
    //purely abstract
  },

  pause: function() {
    //TODO
    //purely abstract
  },

  //returns void
  //className, id - String
  //start, end - float
  //pauseOnExit - boolean
  //enterCallback, exitCallback - function
  addCueRange: function(className, id, start, end, pauseOnExit, enterCallback, exitCallback) {
    if (!this.cueRanges[className]||this.cueRanges[className]==null) {
      this.cueRanges[className] = [];
    }

    var cueRange = {
      start: start,
      end: end,
      pauseOnExit: pauseOnExit,
      enterCallback: enterCallback,
      exitCallback: exitCallback,
    };
    this.cueRanges[className].push(cueRange);
  },

  removeCueRange: function(className) {
    //reset cue ranges
    cueRanges[className] = [];
  },

  //private
  cueRanges: {},
  lastPosition: 0.0,

  checkCueRanges(currentPosition) {
    var entering = [];
    var exititing = [];
    for (className in this.cueRanges) {
      if (this.cueRanges.hasOwnProperty(className)) {
        var cues = this.cueRanges[className];
        for (var i=0;i<cues.length;i++) {
          var cue = cues[i];
          if (currentPosition>this.lastPosition) {
            if (cue.start>this.lastPosition&&cue.start<currentPosition) {
              entering.push(cue);
            }
            if (cue.end>this.lastPosition&&cue.end<currentPosition) {
              exititing.push(cue);
            }
          } else if (currentPosition<this.lastPosition) {
            if (cue.end>lastPosition&&cue.end<currentPosition) {
              entering.push(cue);
            }
            if (cue.start>lastPosition&&cue.start<currentPosition) {
              exititing.push(cue);
            }
          }
        }
      }
    }
    //call the entering events
    for (var i=0;i<entering.length;i++) {
      if (entering[i].enterCallback) {
        entering[i].enterCallback.call(this, entering[i].id);
      }
    }
    //call the exiting events
    for (var i=0;i<exiting.length;i++) {
      if (exiting[i].exitCallback) {
        exiting[i].exitCallback.call(this, exiting[i].id);
      }
      if (exiting[i].pauseOnExit) {
        this.pause();
      }
    }
  }
});

var HTMLVideoElement = HTMLMediaElement.extend({
    width: "0px",
    height: "0px",
    videoWidth: 0,
    videoHeight: 0,
    //poster frame
    poster: null,

    init: function(element) {
      this._super(element);
      this.height = this.domElement.getAttribute("height");
      this.width = this.domElement.getAttribute("width");
      this.poster = this.domElement.getAttribute("poster");
    }
});

var HTMLAudioElement = HTMLMediaElement.extend({
    //no additional properties
    init: function(element) {
      this._super(element);
    },

    canPlayType: function(type) {
      if (type.match(/^audio\/mp3/)) {
        return "probably";
      }
      if (type.match(/^audio\/wav/)) {
        return "probably";
      }
      if (type.match(/^audio\/(x-)?aiff/)) {
        return "probably";
      }
      if (type.match(/^audio\//)) {
        return "maybe";
      }
      return "no";
    }
});

function getHTML5Tags() {
  var audios = document.getElementsByTagName("audio");
  var videos = document.getElementsByTagName("video");
  var html5tags = [];
  for (var i=0;i<audios.length;i++) {
    html5tags.push(new HTMLAudioElement(audios[i]));
  }

  for (var i=0;i<videos.length;i++) {
    html5tags.push(new HTMLVideoElement(audios[i]));
  }
  return html5tags;
}
<|endoftext|>


<commit_before>'use strict'

const api = module.exports = require('express').Router()

api
  .use('/auth', require('./auth'))
  .use('/users', require('./users'))
  .use('/products', require('./products'))
  .use('/categories', require('./categories'))
  .use('/orders', require('./orders'))

// No routes matched? 404.
api.use((req, res) => res.status(404).end())
<commit_msg>products routes<commit_after>'use strict'

const api = module.exports = require('express').Router()

api
  .use('/auth', require('./auth'))
  .use('/users', require('./users'))
  .use('/products', require('./products'))
  .use('/categories', require('./categories'))
  //.use('/orders', require('./orders'))

// No routes matched? 404.
api.use((req, res) => res.status(404).end())
<|endoftext|>