Undeclared document
This is because jslint doesn’t assume that the code is being run in a web browser. We can easily tell it that by placing a directive at the top of the code:
/*jslint browser */
If we’re using a local version of JSLint to lint our code, that directive can be placed in a local config file instead, but for now it’s at the top of the code.
Click handler out of scope.
That’s an init section of code, which belongs below the functions.
// other functions are above here
function clickHandler(event) {
...
}
cnv.addEventListener("click", clickHandler);
cnv.addEventListener("mousemove", moveHandler);
//addEventListener() sets up a function to be called whenever the specified event is delivered to the target
This function needs a “use strict” pragma.
It’s possible to put "use strict"
in all such functions, but a better solution is to use it once at an upper level instead. That’s best done by using an Immediately Invoked Function Expression (called an IIFE), which also helps to protect other code from the effects of your own too.
(function iife() {
"use strict";
// rest of code in here
}());
Unexpected ‘for’
JSLint prefers to not use for statements when better solutions are at hand. Here’s the existing code:
var i = {};
var j = {};
for (i = 0; i <= 20; i += 1) { //rows //20 * size = 500
for (j = 0; j <= 20; j += 1) { //columns
box[i.j] = ctx.rect(i * size, j * size, size, size);
}
}
To work it needs to be [i][j] instead of [i.j], and instead of using a for loop we can use the array forEach method instead.
box.forEach(function (row, i, box) {
row.forEach(function (ignore, j) {
box[i][j] = ctx.rect(i * size, j * size, size, size);
});
});
We just need the box array to be 20x20 to start with now.
Normally with JSLint an array of a defined size is created with:
var items = new Array(20).fill(0);
If we try that with a nested array though we end up with trouble:
// problem code, do not use
var box = new Array(20).fill(
new Array(20).fill(0)
);
The problem with the above code is that each row will point to the same exact array, so when you update the first item of a row, every row ends up with that same exact item.
What we can do instead is to map over each row, replacing it with a new array of our desired size.
var box = new Array(20).fill([])
.map(function () {
return new Array(20).fill(0);
});
And, if we use the ES6 arrow notation, the code then becomes as easy as the following:
var box = new Array(20).fill([])
.map(() => new Array(20).fill(0));
‘drawDot’ is out of scope.
The function is used before it exists. While function declarations are immune to this issue, refactoring them to function expressions results in the problem occurring, so it’s safest to have functions appear first before the code that uses them.
This just means pushing the call to drawDot() down to the end of the Line function.
function Line(x, y) {
var dotSize = 5; //current state is image data of canvas
var currentState = ctx.getImageData(0, 0, cnv.width, cnv.height);
// drawDot(x, y);
// functions defined here
drawDot(x, y);
}
Unexpected ‘this’.
The this
keyword causes too many confusions when it comes to understanding scope. Replace this
with Line and the code is easier to understand.
// this.close = function (x, y) {
Line.close = function (x, y) {
...
// this.drawBuffer = function (x, y) {
Line.drawBuffer = function (x, y) {
Unexpected ‘for’.
When there isn’t a clearly defined array to work with, for statements can be returned back to a while loop instead:
// var i = {};
var i = 0;
// for (i = 0; i < cnv.width; size += 1) { //check if line is in same column
while (i < cnv.width) { //check if line is in same column
if (x1 >= (i - 1) * size && x1 <= (i * size) && x2 >= (i - 1) * size && x2 <= (i * size)) {
move = 1; //line is in the same column
}
size += 1;
}
Did you notice though that the for loop (and the while loop) will never end, because the condition is checking i, but i never changes.
As size is initialized to a set value earlier on, I’m going to update the increment from size
to i
until that can be investigated further.
// size += 1;
i += 1;
The m and b variables defined in that function don’t seem to be used for anything yet either, which requires further investigation too.
Expected ‘===’ and instead saw ‘==’.
The double equals results in too many unexpected things matching, so the triple equals fixes those problems.
// if (move == 1) {
if (move === 1) {
I’ve updated other occurences of double equals in the code too.
Out of scope
Each of these out of scope issues are dealt with by moving the function up above where it is called.
function noshift() { //line is in the same column
...
}
function shift() { //line crosses more than one column
...
}
function line_algorithm() {
...
if (move === 1) {
noshift();
} else {
shift();
}
}
// function noshift() { //line is in the same column
// ...
// }
// function shift() { //line crosses more than one column
// ...
// }
Expected ‘new’ before ‘Min’.
The min math method is lowercase.
// var y3 = Math.Min(y1, y2);
var y3 = Math.min(y1, y2);
And the same goes for the max method too.
Undeclared ‘b’.
function noshift() { //line is in the same column
...
var x3 = (y3 - b) / m;
...
}
There are a number of potential solutions to this issue. We could move the noshift() and shift() functions inside of the line_algorithm() function so that they share the same scope, or we could pass the b and m variables as arguments to the function. The latter option is the better choice here.
function noshift(m, b) { //line is in the same column
...
}
function shift(m, b) { //line crosses more than one column
...
}
...
if (move === 1) {
noshift(m, b);
} else {
shift(m, b);
}
Expected ‘new’ before ‘Floor’.
It’s all lowercase for floor here too, for example:
// row = Math.Floor(y3 / size) + 1;
row = Math.floor(y3 / size) + 1;
Unexpected ‘for’.
All of the other for loops should be turned into while loops instead. For example:
// for (y3 = Math.min(y1, y2); y3 <= Math.max(y1, y2); y3 += 1) {
while (y3 <= Math.max(y1, y2)) {
//loop starts from smaller y coordinate to bigger y coordinate, inc by 1
row = Math.floor(y3 / size) + 1; //up and down
detection(row, column);
y3 += 1;
}
Undeclared ‘boxes’.
This is the last issue being complained about. What is boxes used for?
var size = 25; //size of boxes
...
if (boxes.indexOf(row + ":" + column + ",") === -1) {
//if last hitcheck ISN'T in list of coordinates then box is filled in
boxes = boxes + row + ":" + column + ","; //adds hitcheck to list
...
var boxes = "";
It looks like boxes is just a string, which should be defined up by the size variable.
var boxes = "";
var size = 25; //size of boxes
Because boxes is now defined, those other statements that empty the string shouldn’t use var.
function noshift(m, b) { //line is in the same column
move = 0;
// var boxes = "";
boxes = "";
...
}
function shift(m, b) { //line crosses more than one column
// var boxes = "";
boxes = "";
...
}
And JSLint reports that there are no more structural problems with the code.
Here’s what the updated JavaScript code looks like:
/*jslint browser */
(function iife() {
"use strict";
//canvas intialized and run first
var cnv = document.getElementById("cnv");
//getElementById() returns an Element object representing the element whose id property matches the specified string
var ctx = cnv.getContext("2d"); //method returns a drawing context on the canvas
var line = false; //no line at start
var x1 = {};
var y1 = {};
var x2 = {};
var y2 = {};
var counter = 0; //storing point coordinates
var move = 0; //for determining if line is in the same column
var boxes = "";
var size = 25; //size of boxes
//functions running in order
//--------------
//draw_grid runs at start
//on event starts, click_event > line > draw_dot > line_buffer > line_close > draw_line > line_algorithm > shift || noshift > detection
var box = new Array(20).fill([])
.map(() => new Array(20).fill(0));
function drawGrid() {
box.forEach(function (row, i, box) {
row.forEach(function (ignore, j) {
box[i][j] = ctx.rect(i * size, j * size, size, size);
});
});
}
drawGrid();
function moveHandler(event) {
if (line) { //line buffers to where mouse is
line.drawBuffer(event.layerX, event.layerY);
}
}
function Line(x, y) {
var dotSize = 5; //current state is image data of canvas
var currentState = ctx.getImageData(0, 0, cnv.width, cnv.height);
function resetState() { //resets grid for buffering
ctx.clearRect(0, 0, cnv.width, cnv.height);
ctx.putImageData(currentState, 0, 0);
}
function drawLine(x, y) {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y);
ctx.stroke();
}
function drawDot(x, y) {
ctx.beginPath();
ctx.arc(x, y, dotSize, 0, 2 * Math.PI);
//ctx.arc(x, y, radius, startAngle, endAngle [, anticlockwise]);
ctx.stroke();
}
Line.close = function (x, y) {
resetState();
drawLine(x, y);
return false;
};
Line.drawBuffer = function (x, y) {
resetState();
drawDot(x, y);
drawDot(x, y);
ctx.setLineDash([5, 10]);
drawLine(x, y);
ctx.setLineDash([]);
};
drawDot(x, y);
}
function detection(row, column) { //hitcheck list string starts off as ""
if (boxes.indexOf(row + ":" + column + ",") === -1) {
//if last hitcheck ISN'T in list of coordinates then box is filled in
boxes = boxes + row + ":" + column + ","; //adds hitcheck to list
ctx.fillStyle = "rgba(225,225,225,0.5)"; //changes box opacity
box[row][column] = ctx.fillRect(25, 72, 32, 32);
}
}
function noshift(m, b) { //line is in the same column
move = 0;
boxes = "";
var y3 = Math.min(y1, y2);
var x3 = (y3 - b) / m;
var row = 0;
var column = Math.floor(x3 / size) + 1; //right and left
while (y3 <= Math.max(y1, y2)) {
//loop starts from smaller y coordinate to bigger y coordinate, inc by 1
row = Math.floor(y3 / size) + 1; //up and down
detection(row, column);
y3 += 1;
}
}
function shift(m, b) { //line crosses more than one column
boxes = "";
var x3 = Math.min(x1, x2);
var y3 = 0;
var row = 0;
var column = 0;
while (x3 <= Math.max(x1, x2)) {
//loop starts from smaller x coordinate to bigger x coordinate, inc by 1
y3 = m * x3 + b; //y = mx + b;
row = Math.floor(y3 / size) + 1;
//math.floor gets lowest possible integer, 5.3 to 5
column = Math.floor(x3 / size) + 1;
//row and column are x and y of a box hit, use smaller coordinates
detection(row, column);
x3 += 1;
}
}
function line_algorithm() {
var m = ((y1 - y2) / (x1 - x2)); //slope
var b = (y1 - (m * x1)); //y intercept
var i = 0;
while (i < cnv.width) { //check if line is in same column
if (x1 >= (i - 1) * size && x1 <= (i * size) && x2 >= (i - 1) * size && x2 <= (i * size)) {
move = 1; //line is in the same column
}
i += 1;
}
if (move === 1) {
noshift(m, b);
} else {
shift(m, b);
}
}
function clickHandler(event) {
if (!line) { //no line, starts line function
line = new Line(event.layerX, event.layerY);
} else { //line drawn from first point to second point
line = line.close(event.layerX, event.layerY);
}
if (counter === 0) { //stores first point coordinates
counter = 1;
x1 = event.clientX - ctx.canvas.offsetLeft;
y1 = event.clientY - ctx.canvas.offsetTop;
} else { //stores second point coordinates
counter = 0;
x2 = event.clientX - ctx.canvas.offsetLeft;
y2 = event.clientY - ctx.canvas.offsetTop;
line_algorithm();
}
}
cnv.addEventListener("click", clickHandler);
cnv.addEventListener("mousemove", moveHandler);
//addEventListener() sets up a function to be called whenever the specified event is delivered to the target
}());
Now that the linting has dealt with a majority of the issues, it’s just a matter now of getting the code to work…