-
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
- you can attach an
-
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
-
Don't forget to remove the code from
main.js
-
Set sub namespaces and modules directly on your namespace:
// Ok, but not necessary var view = (function($) { }; JS.view = view; // Better JS.view = (function($) { };
-
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; } }
-
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); }
-
Don't put your click processing in the
setInterval()
game loop! -
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; } }); };
-
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(); },
-
Careful with the
var
keyword. Still declares global variables inside an IIFE.// Global $squares = $(".square"); // Local var $squares = $(".square");
-
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); } };
-
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); } };
-
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(); };
-
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); } };
-
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); };
-
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); } });
-
Beware of infinite recursion:
function activateRandomSquare() { var randIndex = _.random(0, _dataSquares.length - 1) if(_dataSquares[randIndex] === 0) { _dataSquares[randIndex] = 1; } else { activateRandomSquare(); } }
-
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)
-
Don't use the var keyword when creating properties on an object!!!!
// Nope var JC.model = {}; // Yep JC.model = {}
-
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); } };
-
Don't forget to comment out the
main.js
script inindex.html
!
<!-- Main, comment out this script tag once you have your MVC app running -->
<!-- <script src="js/main.js"></script> -->
-
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); } };
-
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); }
-
Watch out for invalid syntax:
var JC = JC || {model, view};
-
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); }
-
Don't forget to use
()
when calling functions!$(document).ready(function() { JC.controller.init; });
-
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; } };
-
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()); };
-
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; } };
-
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(); });