Skip to content

Instantly share code, notes, and snippets.

@BideoWego
Last active September 20, 2016 20:06
Show Gist options
  • Save BideoWego/75b44a0188eadb20a5e9e1d647eda5a4 to your computer and use it in GitHub Desktop.
Save BideoWego/75b44a0188eadb20a5e9e1d647eda5a4 to your computer and use it in GitHub Desktop.
JS II Assessment Do's and Don'ts

assessment_js_ii_notes

Questions

  1. How can you include an init function in your module and why is it useful?

    • you can attach an init function to the return value of the module
    • it is useful because it allows the user of the module to initialize it with options
    • it provides a place to run start up code
  2. How is it possible to have private variables and methods in the "module pattern"? Why are those private variables and methods not available outside of the module?

    • using an IIFE you can create a new scope in which the variables and functions defined are not available outside of that scope

Coding

  1. Don't forget to remove the code from main.js

  2. Set sub namespaces and modules directly on your namespace:

    // Ok, but not necessary
    
    var view = (function($) {  };
    
    JS.view = view;
    
    // Better
    
    JS.view = (function($) {  };
  3. Beware of infinite while loops!

    // Ticking time bomb!
    while (dataSquares[rand] === 1) {
      rand = Math.floor(Math.random()*9);
    }
    
    
    // Good
    var indexes = _shuffledIndexes();
    for (var i = 0; i < indexes.length; i++) {
      var index = indexes[i];
      if (_squares[index] === 0) {
        _squares[index] = 1;
        break;
      }
    }
  4. Data should come from the model, not the view:

    // Where is points being used?
    // Score is incremented from the value stored in the DOM!
    
    stub.updateScore = function(points) {
      var score = $score.text();
      score = parseInt(score);
      $score.empty();
      $score.text(score + 10);
    };
    
    
    // Update the score after a click from the model
    
    // Controller:
    var _onSquareClick = function(index) {
      model.processSquareClick(index);
    
      var dataSquares = model.getSquares();
      var points = model.getScore();
    
      view.updateSquares(dataSquares);
      view.updateScore(points);
    };
    
    // View:
    updateScore: function(points) {
      $('#score').text(points);
    }
  5. Don't put your click processing in the setInterval() game loop!

  6. Don't forget to pass the view event listener callback functions from the controller! If you didn't you had no way to send messages to the controller from the view.

    stub.init = function() {
      $game.click('.square', function(e) {
        var $square = $(e.target);
        console.log(e.target);
        if ($square.hasClass('active')) {
          $square.removeClass('active');
          
          
          tindex = parseInt($square.attr('data-id'));
          //tell contr
    
        } else {
          console.log("hi");
          tindex = undefined;
        }
     });
    };
  7. A word of caution, returning private variables that are objects provides access to them unless you return a duplicate.

    // Returns the private `_squares`
    getSquares: function(){
      return _squares;
    },
    
    
    // Returns a copy of `_squares`
    getSquares: function(){
      return _squares.slice();
    },
  8. Careful with the var keyword. Still declares global variables inside an IIFE.

    // Global
    $squares = $(".square");
    
    // Local
    var $squares = $(".square");
  9. Beware of accidentally nesting functions that you didn't mean to nest. They will not be accessible in the scope you think they are:

    var clickUp = function(onClick) {
      var $game = $('#game');
      $game.click('.square', function(e) {
        var $square = $(e.target);
        if ($square.hasClass('active')) {
          $square.removeClass('active');
          var index = parseInt($square.attr('data-id'));
          onClick(index);
        }
      });
    
      var updateSquares = function(squares) {
        var $squares = $('.square');
        for (var i = 0; i < squares.length; i++) {}
      };
    
      var updateScore = function(points) {
        var $score = $('#score');
        $score.text(points);
      };
    }
    
    return {
      init: function(onClick) {
        setUp();
        clickUp(onClick);
      },
      updateSquares: function(squares) {
        updateSquares(squares);
      },
      updateScore: function(points) {
        updateScore(points);
      }
    };
  10. Don't simply pass the view the model method as a callback, allow the controller to mediate and perform relavant operations between the model and view:

    // Not great
    return {
      init: function() {
        view.init(model.processSquareClick);
        setInterval(interval, 1000);
      }
    };
    
    
    // Better
    
      var _onSquareClick = function(index) {
        model.processSquareClick(index);
    
        var dataSquares = model.getSquares();
        var points = model.getScore();
    
        view.updateSquares(dataSquares);
        view.updateScore(points);
      };
    
    
      var _gameLoop = function() {
        model.activateRandomSquare();
        var dataSquares = model.getSquares();
        view.updateSquares(dataSquares);
      };
    
    
      return {
        init: function() {
          view.init(_onSquareClick);
          setInterval(_gameLoop, 1000);
        }
      };
  11. Don't forget to pass parameters that your functions expect!

      var _listenClick = function(onClick) {
        _$game.click('.square', onClick);
      };
    
      var updateSquares = function(dataSquares) {
        dataSquares.forEach(function(dataSquare,index) {
          var $square = _$squares.eq(index);
          if (dataSquare === 0) {
            $square.addClass('active');
          }
        });
      };
    
      var updateScore = function(points) {
        _$score.text(points);
      };
    
      var init = function(onClick) {
        _cacheDOM();
        _$squares.attr('data');
        _$squares.each(function(index, element) {
          var $element = $(element);
          $element.attr('data-id', index);
        });
        _listenClick();
      };
  12. RED FLAG!!!! Using jQuery in the controller. Should go in the view.

    var onClick = function(e) {
      var $square = $(e.target);
      if ($square.hasClass('active')) {
        $square.removeClass('active');
        var index = parseInt($square.attr('data-id'));
        squareIndex = index;
        model.processSquareClick(squareIndex);
      }
    };
  13. Don't forget to update your squares after click!

    _onClick = function(index){
      model.processSquareClick(index);
      var score = model.getScore();
      view.updateScore(score);
      
      // IMPORTANT!
      var squares = model.getSquares();
      view.updateSquares(squares);
    };
  14. Click event is the only place the square can be removed, so put the class removal there and not in the update loop.

    // Not necessary
    updateSquares = function(dataSquares){
      for(var i = 0; i < dataSquares.length; i++){
        var $square = _$squares.eq(i);
        var dataSquare = dataSquares[i];
        if (dataSquare === 1) {
          $square.addClass('active');
        }
        else if($square.hasClass('active')){
          $square.removeClass('active');
        }
      }
    };
    
    
    // Preferred
    
    $('#game').click('.square', function(e) {
      var $square = $(e.target);
      if ($square.hasClass('active')) {
        $square.removeClass('active');
        var index = parseInt($square.attr('data-id'));
        onClick(index);
      }
    });
  15. Beware of infinite recursion:

    function activateRandomSquare() {
      var randIndex = _.random(0, _dataSquares.length - 1)
      if(_dataSquares[randIndex] === 0) {
        _dataSquares[randIndex] = 1;
      } else {
        activateRandomSquare();
      }
    }
  16. Indexing into a jQuery collection gets you the HTML object, not a jQuery object! Use $(selector).eq(index).

    // HTML object
    $square[i]
    
    // jQuery object
    $square.eq(i)
  17. Don't use the var keyword when creating properties on an object!!!!

    // Nope
    var JC.model = {};
    
    
    // Yep
    JC.model = {}
  18. Even though the callback is passed to the view, keep DOM manipulation out of the controller by having the callback only execute code having to do with talking to the model and view, not DOM manipulation.

    // Belongs in view
    var _onClick = function() {
      $('.square').click(function(e) {
        var $div = $(e.target);
        var index = $div.attr('data-id');
        JC.controller.passSqareClick(index);
      });
    };
    
    return {
      init: function() {
        view.init(_onClick);
        gameLoop();
      },
    
      passSqareClick: function(index) {
        model.processSquareClick(index);
        view.updateSquares(model.getSquares());
        view.updateScore(model.getScore());
      }
    
    }
    
    
    // Only mediation in controller
    var _onSquareClick = function(index) {
      model.processSquareClick(index);
    
      var dataSquares = model.getSquares();
      var points = model.getScore();
    
      view.updateSquares(dataSquares);
      view.updateScore(points);
    };
    
    
    var _gameLoop = function() {
      model.activateRandomSquare();
      var dataSquares = model.getSquares();
      view.updateSquares(dataSquares);
    };
    
    
    return {
      init: function() {
        view.init(_onSquareClick);
        setInterval(_gameLoop, 1000);
      }
    };
  19. Don't forget to comment out the main.js script in index.html!

<!-- Main, comment out this script tag once you have your MVC app running -->
<!-- <script src="js/main.js"></script> -->
  1. Remember that setTimeout will fire and create an interval immediately. Store it in a function to call later on document ready.

    // Evaluates immediately in the IIFE
    stub.gameLoop = setInterval(function(){
      model.activateRandomSquare();
      view.updateSquare(model.getSquares());
    }, 1000);
    
    
    // Waits to be called on init
    return {
      init: function() {
        view.init(_onSquareClick);
        setInterval(_gameLoop, 1000);
      }
    };
  2. Don't treat your view like the model:

    // Accessing score from DOM
    var updateScore = function() {
      var score = $score.text();
      score = parseInt(score);
      $score.text(score + 10);
    }
    
    
    // Passing score to view from model
    // in the controller
    var _onSquareClick = function(index) {
      model.processSquareClick(index);
    
      var dataSquares = model.getSquares();
      var points = model.getScore();
    
      view.updateSquares(dataSquares);
      view.updateScore(points);
    };
    
    updateScore: function(points) {
      $('#score').text(points);
    }
  3. Watch out for invalid syntax:

    var JC = JC || {model, view};
  4. Setting the interval to 1 second is probably more simple than trying to do a frame rate for this app:

    var _playGame = function() {
      view.updateSquares(model.getSquares());
      if (count % 20 === 0) {
        model.activateRandomSquare();
      }
      count += 1;
    };
    
    var init = function () {
      view.init(_onClick);
      setInterval(_playGame, 50);
    }
  5. Don't forget to use () when calling functions!

    $(document).ready(function() {
      JC.controller.init;
    });
  6. If you have jQuery in the controller, something is wrong!!! $ goes in the view:

    var _onClick = function(e) {
      var $square = $(e.target);
      if ($square.hasClass('active')) {
        $square.removeClass('active');
        var index = parseInt($square.attr('data-id'));
        // _dataSquares[index] = 0;
        return index;
      }
    };
  7. Don't forget to update the view after the click event!

    // No updating the view
    var onClick = function(id) {
      model.processSquareClick(id);
    };
    
    
    
    // Good
    var onClick = function(id) {
      model.processSquareClick(id);
      var squares = model.getSquares();
      console.log(squares);
      view.updateSquares(squares);
      view.updateScore(model.getScore());
    };
  8. No jQuery in the model! Belongs in the view:

    var processSquareClick = function(index) {
      var $square = $('.square [data-id=' + index + ']');
    
      if ($square.hasClass('active')) {
        $square.removeClass('active');
        _dataSquares[index] = 0;
        _score += 10;
      }
    };
  9. Don't forget to pass parameters to the functions that expect them!

    // Need to pass square ID to anonymous function
    view.init(function() {
    
      // Need to pass square ID here
      model.processSquareClick();
      _render();
    });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment