javascript variable scope/closure in loop after timeout - javascript

It's late and the part of my brain where Douglas Crockford lives is closed. Ive tried a few things but nothing's doing as expected.
I've got a canvas where I draw a 2 lines, then fade them out on a timer but only the last line in the loop is being faded out. Here's my fiddle, look down to line 50ish in the JS, to see it in action drag your mouse around in the bottom right pane:
http://jsfiddle.net/mRsvc/4/
this is the function, basically the timeout only gets the last value in the loop, I've seen this before and I'm sure if I wasn't so delirious it might be simpler. Here's the function in particular:
function update()
{
var i;
this.context.lineWidth = BRUSH_SIZE;
this.context.strokeStyle = "rgba(" + COLOR[0] + ", " + COLOR[1] + ", " + COLOR[2] + ", " + BRUSH_PRESSURE + ")";
for (i = 0; i < scope.painters.length; i++)
{
scope.context.beginPath();
var dx = scope.painters[i].dx;
var dy = scope.painters[i].dy;
scope.context.moveTo(dx, dy);
var dx1 = scope.painters[i].ax = (scope.painters[i].ax + (scope.painters[i].dx - scope.mouseX) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dx -= dx1;
var dx2 = scope.painters[i].dx;
var dy1 = scope.painters[i].ay = (scope.painters[i].ay + (scope.painters[i].dy - scope.mouseY) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dy -= dy1;
var dy2 = scope.painters[i].dy;
scope.context.lineTo(dx2, dy2);
scope.context.stroke();
for(j=FADESTEPS;j>0;j--)
{
setTimeout(function()
{
var x=dx,y=dy,x2=dx2,y2=dy2;
scope.context.beginPath();
scope.context.lineWidth=BRUSH_SIZE+1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth=BRUSH_SIZE;
},
DURATION/j);
}
}
}

The problem is that the variables dx, dy, etc that you refer to in the function you pass to setTimeout() are defined in the surrounding scope and by the time any of the timeouts actually runs these variables all hold the values from the last iteration of the loop(s).
You need to create an extra containing function to close over the values from each iteration. Try something like the following:
for(j=FADESTEPS;j>0;j--) {
(function(x,y,x2,y2) {
setTimeout(function() {
scope.context.beginPath();
scope.context.lineWidth=BRUSH_SIZE+1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth=BRUSH_SIZE;
},
DURATION/j);
})(dx, dy, dx2, dy2);
}
This creates a new anonymous function for each iteration of the j=FADESTEPS loop, executing it immediately and passing the dx, etc. values as they were at the time each iteration of the loop ran, and moving the x, y, etc. variables out of your existing function and making them parameters of the new one so then by the time the timeout runs it will use the correct values.

You can try something like this:
`<script>
for(j=10;j>0;j--)
{
var fn = function(ind){return function()
{
console.log(ind);
};
}(j);
setTimeout(fn,
1000);
}
</script>`

Or another way (as soon as you do not use IE, but let it learn canvas at first :))
for(j=FADESTEPS;j>0;j--)
{
setTimeout(function(x,y,x2,y2)
{
scope.context.beginPath();
scope.context.lineWidth=BRUSH_SIZE+1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth=BRUSH_SIZE;
},
DURATION/j,dx,dy,dx2,dy2);
}
ps: there is no need in set of extra functions (the reasons are clear)

First of all j is a global.
Second of all, you never close the paths that you begin, which can cause memory leaks. It seems really slow and this may be why. You need to call closePath() whenever you're done with the paths you start with beginPath()
Next, I think there's some general funniness with how this works. You're fading out by drawing over the last thing with white. I've done something similar to this before, but instead I cleared the whole screen and kept drawing things over and over again. It worked okay for me.
Explanation
The other answers about dx and dy being passed from the higher scope are the right answers though. Async functions defined in synchronous for loops will take the last version of the state.
for (var i = 0; i < 10; i++) setTimeout(function() { console.log(i)}, 10 )
10
10
// ...

I would suggest you to use an array and store the points avoiding setTimeOut call in a loop. Somewhat like this.
this.interval = setInterval(update, REFRESH_RATE);
var _points = [];
function update() {
var i;
this.context.lineWidth = BRUSH_SIZE;
this.context.strokeStyle = "rgba(" + COLOR[0] + ", " + COLOR[1] + ", " + COLOR[2] + ", " + BRUSH_PRESSURE + ")";
for (i = 0; i < scope.painters.length; i++) {
scope.context.beginPath();
var dx = scope.painters[i].dx;
var dy = scope.painters[i].dy;
scope.context.moveTo(dx, dy);
var dx1 = scope.painters[i].ax = (scope.painters[i].ax + (scope.painters[i].dx - scope.mouseX) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dx -= dx1;
var dx2 = scope.painters[i].dx;
var dy1 = scope.painters[i].ay = (scope.painters[i].ay + (scope.painters[i].dy - scope.mouseY) * scope.painters[i].div) * scope.painters[i].ease;
scope.painters[i].dy -= dy1;
var dy2 = scope.painters[i].dy;
scope.context.lineTo(dx2, dy2);
scope.context.stroke();
_points.push([dx, dy, dx2, dy2]);
clear();
}
}
function clear(){
if(_points.length < FADESTEPS){
return;
}
var p = _points.shift();
if(!p){
return;
}
var x = p[0],
y = p[1],
x2 = p[2],
y2 = p[3];
scope.context.beginPath();
scope.context.lineWidth = BRUSH_SIZE + 1;
scope.context.moveTo(x, y);
scope.context.strokeStyle = "rgba(" + 255 + ", " + 255 + ", " + 255 + ", " + .3 + ")";
scope.context.lineTo(x2, y2);
scope.context.stroke();
scope.context.lineWidth = BRUSH_SIZE;
}
I know this is not exactly what you need, but I think this can be modified to get it.

Related

Nodejs - prevent socket.io from dropping the frame rates

I'm trying to program some animations in html5 canvas. I need the animations to be reproduced on any other client connected to my server. So what I do is I send the function to be called and the arguments as a string and call eval() on the client side. That way the animation logic needs to be done only on one canvas, while the function calls that actually render things are executed by all clients.
However, my frame rates drop drastically when I do this. I use socket.emit() to send signals to the server which in turn calls socket.broadcast.emit() to send the string to all clients. This is the rendering loop :
var rf = function()
{
// clear background
context.fillStyle = "#000";
context.fillRect(0, 0, width, height);
socket.emit('action', 'clearScreen', "{}");
// mouse position to head towards
var cx = (mousex - width / 2) + (width / 2),
cy = (mousey - height / 2) + (height / 2);
// update all stars
var sat = Floor(Z * 500); // Z range 0.01 -> 0.5
if (sat > 100) sat = 100;
for (var i=0; i<units; i++)
{
var n = stars[i], // the star
xx = n.x / n.z, // star position
yy = n.y / n.z,
e = (1.0 / n.z + 1) * 2; // size i.e. z
if (n.px !== 0)
{
// hsl colour from a sine wave
context.strokeStyle = "hsl(" + ((cycle * i) % 360) + "," + sat + "%,80%)";
context.lineWidth = e;
context.beginPath();
socket.emit('action', 'context.beginPath', "{}");
context.moveTo(xx + cx, yy + cy);
socket.emit('action', 'context.moveTo', "{\"a\": [" + (xx + cx) + "," + (yy + cy) + "]}");
context.lineTo(n.px + cx, n.py + cy);
socket.emit('action', 'context.lineTo', "{\"a\": [" + (n.px + cx) + "," + (n.py + cy) + "]}");
context.stroke();
socket.emit('action', 'context.stroke', "{}");
}
// update star position values with new settings
n.px = xx;
n.py = yy;
n.z -= Z;
// reset when star is out of the view field
if (n.z < Z || n.px > width || n.py > height)
{
// reset star
resetstar(n);
}
}
// colour cycle sinewave rotation
cycle += 0.01;
requestAnimFrame(rf);
};
requestAnimFrame(rf);
The above snippet was taken from here.
Can you suggest ways to prevent the frame rates from dropping? I guess this can be done if socket.emit was non-blocking.
Sending strings that reproduces the frame is the lightest way to accomplish what i want. Sending pixels are even more heavy. Sending strings works fine when the frame is easy to draw - for example a simple circle moving up and down renders fine.
I've been down the road you're traveling now. It's an interesting & fun road, but sometimes a frustrating road. Have fun and be patient!
OK...here are a few hints:
All your drawing commands must be atomic. Each command sent to any client must define a complete drawing operation (a complete path operation from beginPath through stroke).
Make your clients smart. Give each client at least enough javascript smarts to draw each primitive shape after being given a primitive definition. Client side functions: drawLine(), drawRect(), drawCircle(), drawBCurve(), drawQCurve(), drawText(), etc.
Emits can be lost. This is why it's not a good idea to just send raw context commands. Include a serial index# in each command object and, after processing each command array, append that new array to a master array (the master array contains all arrays ever received). This way the client can recognize and request replacements of missing packets. Hint: you can even use this master array to replay the entire animation ;-)
Here are some example snippets which expand my hints (not complete or tested!):
On the computer issuing the drawing commands
// emits can be lost
// include an index# with each command so clients
// can request replacements for lost commands
var nextCommand=1;
// an array to hold all commands to be sent with the next emit
var commands=[];
// Example: push 1 atomic line command into the commands array
commands.push({
op:'line',
x0:xx+cx,
y0:yy+cy,
x1:n.px+cx,
y1:n.py+cy,
stroke:"hsl("+((cycle*i)%360)+","+sat+"%,80%)",
fill:null,
index:(nextCommand++),
});
// Continue adding commands to be sent in this emit
// Push any more atomic drawing commands into commands[]
// You will probably generate many commands through a loop
// serialize the commands array and emit it
socket.emit(JSON.stringify(commands));
On each client
// context color changes are expensive
// Don't change the color if the context is already that color
// These vars hold the current context color for comparison
var currentStroke='';
var currentFill='';
// deserialize the incoming commands back into an array of JS command objects
var commands=JSON.parse(receivedCommands);
// process each command in the array
for(var i=0;i<commands.length;i++){
var cmd=commands[i];
// execute each command's atomic drawing based on op-type
switch(cmd.op){
case 'line':
drawLine(cmd);
break;
// ... also 'circle','rect',etc.
}
}
// draw a line defined by an atomic line drawing command
function drawLine(cmd){
if(stroke){
// execute primitive line commands
context.beginPath();
context.moveTo(cmd.x0,cmd.y0);
context.lineTo(cmd.x1,cmd.y1);
// Don't change the color if the context is already that color
if(stroke!==currentStroke){
context.strokeStyle=stroke;
currentStroke=stroke;
}
// stroke the line (this completes an atomic line draw--beginPath thru stroke)
context.stroke();
}
}
Emit only once per frame, sending an object actions containing all the informations
var rf = function()
{
var actions = {};//prepare the object to be sent
// clear background
context.fillStyle = "#000";
context.fillRect(0, 0, width, height);
//socket.emit('action', 'clearScreen', "{}");
actions['clearScreen'] = "{}";
// mouse position to head towards
var cx = (mousex - width / 2) + (width / 2),
cy = (mousey - height / 2) + (height / 2);
// update all stars
var sat = Floor(Z * 500); // Z range 0.01 -> 0.5
if (sat > 100) sat = 100;
actions['stars'] = [];//push all the star related actions in this array
for (var i=0; i<units; i++)
{
var n = stars[i], // the star
xx = n.x / n.z, // star position
yy = n.y / n.z,
e = (1.0 / n.z + 1) * 2; // size i.e. z
if (n.px !== 0)
{
// hsl colour from a sine wave
context.strokeStyle = "hsl(" + ((cycle * i) % 360) + "," + sat + "%,80%)";
context.lineWidth = e;
context.beginPath();
//socket.emit('action', 'context.beginPath', "{}");
actions['stars'].push({
'context.beginPath' : "{}"
});
context.moveTo(xx + cx, yy + cy);
//socket.emit('action', 'context.moveTo', "{\"a\": [" + (xx + cx) + "," + (yy + cy) + "]}");
actions['stars'].push({
'context.moveTo' : "{\"a\": [" + (xx + cx) + "," + (yy + cy) + "]}"
});
context.lineTo(n.px + cx, n.py + cy);
//socket.emit('action', 'context.lineTo', "{\"a\": [" + (n.px + cx) + "," + (n.py + cy) + "]}");
actions['stars'].push({
'context.lineTo' : "{\"a\": [" + (n.px + cx) + "," + (n.py + cy) + "]}"
});
context.stroke();
//socket.emit('action', 'context.stroke', "{}");
actions['stars'].push({
'context.stroke' : "{}"
});
}
// update star position values with new settings
n.px = xx;
n.py = yy;
n.z -= Z;
// reset when star is out of the view field
if (n.z < Z || n.px > width || n.py > height)
{
// reset star
resetstar(n);
}
}
// colour cycle sinewave rotation
cycle += 0.01;
//emit only once
socket.emit('actions',actions);
requestAnimFrame(rf);
};
//requestAnimFrame(rf);
rf();//call directly

Get the pixel from coordinates

I have 2 coordinates x and y of a point. I want to calculate the angle between three points, say A,B,C.
Now for the B point I do not have a pixel which contains the 2 coordinates instead I have the pixel, how can I get a single pixel which I can use in my formula.
function find_angle(A,B,C) {
var AB = Math.sqrt(Math.pow(B.x-A.x,2)+ Math.pow(B.y-A.y,2));
var BC = Math.sqrt(Math.pow(B.x-C.x,2)+ Math.pow(B.y-C.y,2));
var AC = Math.sqrt(Math.pow(C.x-A.x,2)+ Math.pow(C.y-A.y,2));
var abc = (BC*BC)+ (AB*AB)-(AC*AC);
var x = abc/(2*BC*AB);
var Angle = FastInt((Math.acos(x) * 180/3.14159));
document.getElementById("Angle").value = Angle;
}
How to proceed with this.
A is changing every time I move the point and I have the updated coordinates as well but I am not able to get the whole pixel I can use in the formula to calculate the new angle.
If I understand what you are asking - you want to create a calculator for the angle formed between
3 dots (A, B middle, C).
Your function should work for the final calculation but you need to recall the function every time
a point has moved.
I created a nice fiddle to demonstrate how you can achieve it with : jQuery, jQuery-ui, html.
I used the draggable() plugin of the UI library to allow the user to manually drag the dots around
And I'm recalculating the angle while dragging.
Take a look: COOL DEMO JSFIDDLE
The CODE ( you will find all HTML & CSS in the demo):
$(function(){
//Def Position values:
var defA = { top:20, left:220 };
var defB = { top:75, left:20 };
var defC = { top:200, left:220 };
//Holds the degree symbol:
var degree_symbol = $('<div>').html('゜').text();
//Point draggable attachment.
$(".point").draggable({
containment: "parent",
drag: function() {
set_result(); //Recalculate
},
stop: function() {
set_result(); //Recalculate
}
});
//Default position:
reset_pos();
//Reset button click event:
$("#reset").click(function(){ reset_pos(); });
//Calculate position of points and updates:
function set_result() {
var A = get_middle("A");
var B = get_middle("B");
var C = get_middle("C");
angle = find_angle(A,B,C);
$("#angle").val(angle + degree_symbol);
connect_line("AB");
connect_line("CB");
}
//Angle calculate:
function find_angle(A,B,C) {
var AB = Math.sqrt(Math.pow(B.x-A.x,2)+ Math.pow(B.y-A.y,2));
var BC = Math.sqrt(Math.pow(B.x-C.x,2)+ Math.pow(B.y-C.y,2));
var AC = Math.sqrt(Math.pow(C.x-A.x,2)+ Math.pow(C.y-A.y,2));
radians = Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB)); //Radians
degree = radians * (180/Math.PI); //Degrees
return degree.toFixed(3);
}
//Default position:
function reset_pos() {
$("#A").css(defA);
$("#B").css(defB);
$("#C").css(defC);
set_result();
}
//Add lines and draw them:
function connect_line(points) {
var off1 = null;
var offB = get_middle("B");
var thickness = 4;
switch (points) {
case "AB": off1 = get_middle("A"); break;
case "CB": off1 = get_middle("C"); break;
}
var length = Math.sqrt(
((offB.x-off1.x) * (offB.x-off1.x)) +
((offB.y-off1.y) * (offB.y-off1.y))
);
var cx = ((off1.x + offB.x)/2) - (length/2);
var cy = ((off1.y + offB.y)/2) - (thickness/2);
var angle = Math.atan2((offB.y-off1.y),(offB.x-off1.x))*(180/Math.PI);
var htmlLine = "<div id='" + points + "' class='line' " +
"style='padding:0px; margin:0px; height:" + thickness + "px; " +
"line-height:1px; position:absolute; left:" + cx + "px; " +
"top:" + cy + "px; width:" + length + "px; " +
"-moz-transform:rotate(" + angle + "deg); " +
"-webkit-transform:rotate(" + angle + "deg); " +
"-o-transform:rotate(" + angle + "deg); " +
"-ms-transform:rotate(" + angle + "deg); " +
"transform:rotate(" + angle + "deg);' />";
$('#testBoard').find("#" + points).remove();
$('#testBoard').append(htmlLine);
}
//Get Position (center of the point):
function get_middle(el) {
var _x = Number($("#" + el).css("left").replace(/[^-\d\.]/g, ''));
var _y = Number($("#" + el).css("top").replace(/[^-\d\.]/g, ''));
var _w = $("#" + el).width();
var _h = $("#" + el).height();
return {
y: _y + (_h/2),
x: _x + (_w/2),
width: _w,
height: _h
};
}
});
This Code requires jQuery & jQuery-UI. Don't forget to include them if you test it locally.
Have fun!

Assigning a variable to the value of style property not working

I must be overlooking something, but the below code is not working for me (sorry for something so basic):
(function(){
document.body.addEventListener("click", function(){
var test = "rgb(" + parseInt((9.3 * Math.random())) + "," + parseInt((9.1 * Math.random())) + "," + parseInt((102 * Math.random())) + ");";
this.style.backgroundColor = test;
console.log(test); // corretly outputs rgb(12, 20, 30)
}, false);
})();
Using a string as the value works and assigning a variable to a string containing an rgb value also works, but using test as the value doesn't work, and doesn't show any console errors. Why is this?
It does not correctly output rgb(12, 20, 30), it outputs rgb(12, 20, 30);
You are setting
this.style.backgroundColor = "rgb(12, 20, 30);";
There is a semicolon which is causing the value to be invalid. You need to remove the semicolon from the string
+ ");";
^
It should be
+ ")";
You don't need to pass ; at the end
var test =
"rgb("
+ parseInt((9.3 * Math.random()))
+ ","
+ parseInt((9.1 * Math.random()))
+ ","
+ parseInt((102 * Math.random())) + ")";
Remove ; from + ");"
DEMO
Also your colour range is not big enough to notice a difference, 12, 30 and 7 will look similar. try this:
(function () {
document.addEventListener('click', function () {
var r = Math.round(Math.random() * 255),
g = Math.round(Math.random() * 255),
b = Math.round(Math.random() * 255);
document.body.style.backgroundColor = 'rgb(' + r + ', ' + g + ', ' + b + ')';
});
})();
Here is a working fiddle:
http://jsfiddle.net/kmturley/7zNv6/

Classifying a Raphael paper into 20x20 divisions

I have a paper with 400 x 500 in size. I am trying to divide this into 20 x 20 pixels with below code
var dpH = 500, dpW = 400, drawPad = new Raphael(document.getElementById('p'),
dpW, dpH);
for ( var i = 0; i <= dpH / 20; i++) {
drawPad.path("M" + 1 + " " + ((i * 20) + 1) + "L" + dpW + " "
+ ((i * 20) + 1));
}
for ( var j = 0; j <= (dpW / 20); j++) {
drawPad.path("M" + ((j * 20) + 1) + " " + 1 + "L" + ((j * 20) + 1) + " "
+ dpH);
}
And HTML markup is like below
<div id="p" style="background-image:url(image.png)"> </div>
with same height and width of Background Image.
My original requirement was making the image.png as Rapheal paper. But I was failed to do that. So I made it as background-image to the DIV#P. Then converted the DIv to Paper.
Here are my questions related to above
Does all the pixels of Background-Image and DIV match with each other?
The way I did above is to classify the total paper into 20x20 pixel divisions. Is that correct way of doing?
What is the width of the drawn line?
Please help me on this.
Ok, so if I understand you correctly; What you really want is to get the raw image data for 20x20 squares of the image.
Here's how you can extract image data with Canvas (also on jsFiddle):
var dpH = 500,
dpW = 400,
ctx = document.getElementById('p').getContext('2d'),
exportData = function() {
var data;
for (var y=0, yl=dpH/20; y<yl; y++) {
for (var x=0, xl=dpW/20; x<xl; x++) {
imgData = ctx.getImageData(x*20, y*20, 20, 20).data;
console.log("Image data for " + x*20 + ", " + y*20, imgData);
// data is an array with 4 values pr pixel
// Top left pixel in the 20x20 square
r = imgData[0]; // red
g = imgData[1]; // green
b = imgData[2]; // blue
a = imgData[3]; // alpha
console.log("RGBa of " + x*20 + ", " + y*20 + ": ", r, g, b, a);
}
}
},
drawImage = function() {
ctx.drawImage(this, 0, 0);
exportData(this);
};
var img = new Image();
img.onload = drawImage;
img.src = "image.png"; // has to be on the same domain
** Original answer **
The result is a DIV with an SVG-element inside, and a background image behind it. The browser (if it supports SVG) will render them on top of each other. Do you want to extract pixel values? If so, you have to do this through HTML5 Canvas instead of SVG.
Sorry, I don't understand. What are you trying to accomplish? Do you want the pixel data for 20x20 squares? With Raphael you are just drawing lines on top of the picture.
The defaut with of a path is 1 pixels. You can change this by setting an attribute on the path. Example (also on jsfiddle.net):
var dpH = 500,
dpW = 400,
drawPad = Raphael(document.getElementById('p'), dpW, dpH),
style = {
"stroke" : "#fff", // white
"stroke-width" : 2 // default 1
};
for ( var i = 0; i <= dpH / 20; i++) {
drawPad.path("M" + 1 + " " + ((i * 20) + 1) + "L" + dpW + " "
+ ((i * 20) + 1)).attr(style);
}
for ( var j = 0; j <= (dpW / 20); j++) {
drawPad.path("M" + ((j * 20) + 1) + " " + 1 + "L" + ((j * 20) + 1) + " "
+ dpH).attr(style);
}​

Odd behavior from raphaelJS when trying to move SVG rects

I am working on a graphics demo using RaphaelJS and jQuery.SVG. Right now I have a dynamically created 8x8 grid of rectangles that users are able to click to select. Once two of the rects are selected, then the selected rects are supposed to switch places with each other (like in Bejeweled.)
I have gotten to the point where I can get the rects to swap, but they are not ending up in the correct positions. According to the code, I am swapping out the x and y coordinates of each selected rect in an animate function. The rects always end up "off the grid" in testing. The visual effect is pretty neat, but I really want the rects to stay lined up.
http://img407.imageshack.us/img407/2222/raphaelerror.jpg
http://imageshack.us/photo/my-images/593/raphaelerror2.jpg/
The debug strings that I am building tell me that the positions of the rects have not changed. Obviously that is not true...what is going on here?
Here's the relevant code for this issue:
// swapTiles
/*
Swaps two tiles on the board that have been set to 'selected' with an animation.
*/
function swapTiles(toSwap) {
var holdX = $(toSwap[0]).attr('x');
var holdY = $(toSwap[0]).attr('y');
// Pre-swap debugging
statusString += "<br><b>BEFORE SWAP</b><br>";
statusString += "<br>First cell: " + selectedCellNames[0];
statusString += "<br>2nd cell: " + selectedCellNames[1];
statusString += "<br>$(toSwap[0]).attr('x'): " + $(toSwap[0]).attr('x').toString();
statusString += "<br>$(toSwap[0]).attr('y'): " + $(toSwap[0]).attr('y').toString();
statusString += "<br>$(toSwap[1]).attr('x'): " + $(toSwap[1]).attr('x').toString();
statusString += "<br>$(toSwap[1]).attr('y'): " + $(toSwap[1]).attr('y').toString();
statusString += "<br>";
/*
This line is using jQuery SVG to grab the DOM elements. This is being used because trying to grab the elements through
raphael was returning undefined errors. There seems to be some DOM scope issue that is causing me to use this and the temp.node
workarounds in order to reference SVG objects created through raphael.
What did not work:
$(toSwap[0]).animate({ x: 50, y: 100, 'stroke-width': 3 }, 250, '<'); --raphael animate function
$(toSwap[0]).node.animate({ x: 50, y: 100, 'stroke-width': 3 }, 250, '<');
When I tried to access the tiles using jQuery and the cellName user-defined attribute, I got '0' added to my debug output.
*/
$(toSwap[0]).animate({ svgX: $(toSwap[1]).attr('x'), svgY: $(toSwap[1]).attr('y'), svgStrokeWidth: 2, svgStroke: "#000" }, 250);
$(toSwap[1]).animate({ svgX: holdX, svgY: holdY, svgStrokeWidth: 2, svgStroke: "#000" }, 250);
// Post-swap debugging
statusString += "<br><b>SWAP COMPLETE</b><br>";
statusString += "<br>$(toSwap[0]).attr('x'): " + $(toSwap[0]).attr('x').toString();
statusString += "<br>$(toSwap[0]).attr('y'): " + $(toSwap[0]).attr('y').toString();
statusString += "<br>$(toSwap[1]).attr('x'): " + $(toSwap[1]).attr('x').toString();
statusString += "<br>$(toSwap[1]).attr('y'): " + $(toSwap[1]).attr('y').toString();
statusString += "<br>";
}
// drawRect
/*
returns a Raphael rectangle object with the inputted options
color = the value that the color attr will be set to for this rect
x, y = x & y position of the top-left corner of the rect
w, h = width and height of the rect
corner = radius for the rounded corners
*/
function drawRect(color, x, y, w, h, corner) {
var temp = canvas.rect(x, y, w, h, corner);
temp.attr("fill", color);
temp.node.setAttribute("selected", "false"); // Will be used to check for selected tiles in main loop
temp.node.setAttribute("cellName", cellName); // Give this rect a name
// Add the mouseover/hover events
temp.node.onmouseover = function () {
temp.animate({ scale: 1.5, stroke: "#FFF", 'stroke-width': 3 }, 250, 'bounce').toFront();
}; // end temp.node.onmouseover
temp.node.onmouseout = function () {
if (temp.node.getAttribute("selected") == "true") {
temp.animate({ scale: 1, stroke: "#FFF", 'stroke-width': 4 }, 250, 'elastic').toFront();
}
else {
temp.animate({ scale: 1, stroke: "#000", 'stroke-width': 2 }, 250, 'elastic').toBack();
}
// Update the status debugging
statusString += "<br>(x: " + x.toString() + ", y: " + y.toString() + ") temp.node.getAttribute('selected') after .node.onmouseout = " + temp.node.getAttribute("selected");
statusString += "<br>selectedCount = " + selectedCount.toString();
statusString += "<br>cellName = " + temp.node.getAttribute("cellName");
statusString += "<br>selectedCells[0] = " + selectedCells[0].toString();
statusString += "<br>selectedCells[1] = " + selectedCells[1].toString();
$("#status").html(statusString);
statusString = "";
}; // end temp.node.onmouseout
// Toggle between selected / not-selected
temp.node.onclick = function () {
// if not selected yet
if (temp.node.getAttribute("selected") != "true") {
temp.animate({ scale: 1, stroke: "#FFF", 'stroke-width': 4 }, 250, 'elastic').toFront();
temp.node.setAttribute("selected", "true");
selectedCount += 1;
// Place the current cell into our array
if (selectedCount == 1) {
selectedCells[0] = temp.node;
selectedCellNames[0] = temp.node.getAttribute("cellName");
}
// We have two cells selected: time to swap
else if (selectedCount == 2) {
selectedCells[1] = temp.node;
selectedCellNames[1] = temp.node.getAttribute("cellName");
swapTiles(selectedCells);
// Clean up selectedCells and the selected attributes
/*
Since selectedCells already contains references to the temp.nodes of each SVG object,
use this format to set the attributes. No jQuery SVG call or additional .node is needed.
*/
selectedCells[0].setAttribute("selected", "false");
selectedCells[1].setAttribute("selected", "false");
selectedCells[0] = "";
selectedCells[1] = "";
selectedCellNames[0] = "";
selectedCellNames[1] = "";
selectedCount = 0;
}
else {
// Something went wrong
statusString += "<br><b>ERROR: selectedCells out of range!</b>";
}
}
// if already selected de-select
else {
temp.animate({ scale: 1, stroke: "#000", 'stroke-width': 2 }, 250, 'elastic').toBack();
temp.node.setAttribute("selected", "false");
selectedCount -= 1;
// Remove the current cell name from our array
if (selectedCount == 0) {
selectedCells[0] = "";
selectedCellNames[0] = "";
}
else if (selectedCount == 1) {
selectedCells[1] = "";
selectedCellNames[1] = "";
}
else {
// Something went wrong
statusString += "<br><b>ERROR: selectedCells out of range!</b>";
}
}
// Update the status debugging
statusString += "<br>(x: " + x.toString() + ", y: " + y.toString() + ") temp.node.getAttribute('selected') after .node.onclick = " + temp.node.getAttribute("selected");
statusString += "<br>selectedCount = " + selectedCount.toString();
statusString += "<br>selectedCells[0] = " + selectedCells[0].toString();
statusString += "<br>selectedCells[1] = " + selectedCells[1].toString();
$("#status").html(statusString);
statusString = "";
}; // end temp.node.onclick
return temp;
/*
NOTES:
temp.node.[function] - the node raphael property lets me directly access the DOM element that I just created
I will need to add all of my onclick and other direct access events for my tiles in this function.
I will get errors if I try to reference the DOM elements from another part of this program, probably because it is only
in this function that I actually create the DOM object. Once this function finishes, then the temp object reference is passed
back into my initialization loop, where at that point I cannot add any more DOM elements.
.animate - Raphael's animation function. Note that the properties that are animated are defined in the first argument,
between a set of {}. The second argument is the time in milliseconds that the animation will take.
.toFront() & .toBack() - These functions move the object to the top or bottom of the drawing stack. In other words, if I use
toFront on an object, it should be drawn on top of everything else, and vice-versa. This eliminates a graphical issue where the
tiles overrlap while expanded.
.node.setAttribute / .getAttribute(key, value) - These are the DOM functions to set and get attributes of specific DOM elements.
I am using these instead of .attr(attr, value) because I can't perform jQuery operations on the DOM elements at this point in the script.
*/
} // end drawRect
// Initialize the grid of rectangles
for (var i = 0; i < grid.length; i++) {
for (var j = 0; j < grid[i].length; j++) {
cellName = "cell " + i + ":" + j;
// drawRect(color, x, y, width, height, corner radius)
grid[i][j] = drawRect(colors[Math.floor(Math.random() * colors.length)], startx + (width * j), starty + (height * i), width, height, cornerRadius);
// used for debugging
debugString += "<br>grid[" + i + "][" + j + "] x: " + (startx + (width * j)) + " y: " + (starty + (height * i));
} // end inner for loop
} // end outer for loop
I'm not a Javascript expert so any help would be appreciated. Thanks!

Categories