How do I access JS functions from an array storage? - javascript

I'm working with custom "buttons" in html5 canvas. Because I've got so many buttons I think it makes more sense to store then in an array. My dilemma is, I'm not quite sure on how to implement custom functions which are 'attached' to a particular button. I've seen this posting, not exactly sure if that is applicable here.
Clearly btn[i].function+"()"; isn't cutting it.
Here's a JSFiddle.
How can I store a custom function within the button array, and successfully call it upon mouseclick?
Code follows:
<!doctype html>
<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body {background-color: black; margin: 8px; }
#canvas {border: 1px solid gray;}
</style>
<script>
$(function() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
var cw = canvas.width;
var ch = canvas.height;
var btn = [{
x: 50,
y: 100,
width: 80,
height: 50,
display: "Hello There",
function: "functionA" // Is this right?
}, {
x: 150,
y: 100,
width: 80,
height: 50,
display: "Why Not?",
function: "functionB" // Is this right?
}, {
x: 250,
y: 100,
width: 80,
height: 50,
display: "Let's Go!",
function: "functionC" // Is this right?
}];
function drawAll() {
ctx.clearRect(0, 0, cw, ch);
for (var i = 0; i < btn.length; i++) {
drawButton(ctx, btn[i].x, btn[i].y, btn[i].width, btn[i].height, btn[i].display);
}
}
drawAll();
// listen for mouse events
$("#canvas").mousedown(function(e) {
handleMouseDown(e);
});
function handleMouseDown(e) {
// tell the browser we'll handle this event
e.preventDefault();
e.stopPropagation();
// save the mouse position
lastX = parseInt(e.clientX - offsetX);
lastY = parseInt(e.clientY - offsetY);
// hit all existing buttons
for (var i = 0; i < btn.length; i++) {
if ((lastX < (btn[i].x + btn[i].width)) &&
(lastX > btn[i].x) &&
(lastY < (btn[i].y + btn[i].height)) &&
(lastY > btn[i].y)) {
// execute button function
btn[i].function+"()"; // obviously this is just wrong.
console.log("Button #" + (i + 1) + " has been clicked!!" );
}
}
}
function drawButton(context, x, y, width, height, text) {
var myGradient = context.createLinearGradient(0, y, 0, y + height);
myGradient.addColorStop(0, '#999999');
myGradient.addColorStop(1, '#222222');
context.fillStyle = myGradient;
context.fillRect(x, y, width, height);
context.fillStyle = 'white';
// textAlign aligns text horizontally relative to placement
context.textAlign = 'center';
// textBaseline aligns text vertically relative to font style
context.textBaseline = 'middle';
context.font = 'bold 15px sans-serif';
context.fillText(text, x + width / 2, y + height / 2);
}
function functionA() {
alert("Yipee Function A!");
}
function functionB() {
alert("Yowza, it's Function B!");
}
function functionC() {
alert("Now showing Function C!");
}
})
</script>
</head>
<body>
<canvas id="canvas" width=400 height=300></canvas>
</body>
</html>

Try to change your buttons to:
{
display: "Hello There",
action: functionA
}
And to invoke:
btn[i].action();
I changed the name function to action because function is a reserved word and cannot be used as an object property name.

You can store references to the functions in your array, just lose the " signs around their names (which currently makes them strings instead of function references), creating the array like this:
var btn = [{
x: 50,
y: 100,
width: 80,
height: 50,
display: "Hello There",
function: functionA
}, {
x: 150,
y: 100,
width: 80,
height: 50,
display: "Why Not?",
function: functionB
}]
Then you can call either by writing btn[i].function().

Don't put the name of the function in the array, put a reference to the function itself:
var btn = [{
x: 50,
y: 100,
width: 80,
height: 50,
display: "Hello There",
'function': functionA
}, {
x: 150,
y: 100,
width: 80,
height: 50,
display: "Why Not?",
'function': functionB
}, {
x: 250,
y: 100,
width: 80,
height: 50,
display: "Let's Go!",
'function': functionC
}];
To call the function, you do:
btn[i]['function']();
I've put function in quotes in the literal, and used array notation to access it, because it's a reserved keyword.

Related

Clear HTML canvas [duplicate]

This question already has answers here:
clearRect function doesn't clear the canvas
(5 answers)
Closed 3 years ago.
I want an HTML canvas that displays mouse position on its JS grid on mouse move, but I can't seem to clear my canvas, I tried using ctx.clearRect(0,0 canvas.width, canvas.height) and clearing with a click, but it somehow remembers the previous draw it had. I want only one black square to be displayed on canvas at a time depending on the mouse position. Heres demo on code pen and some code
<canvas id="myMap" style="width: 300px;height: 300px;background-color: beige;"></canvas>
<script>
var findDivisible = (x, scale) => {
while (x % scale !== 0 && x > 0) {
x = x - 1;
};
return x
};
var map = document.getElementById("myMap");
map.width = 300;
map.height = 300;
var mapContext = document.getElementById("myMap").getContext("2d");
map.addEventListener("mousemove", function(e) {
mapContext.clearRect(0, 0, map.width, map.height);
mapContext.rect(findDivisible(e.clientX - map.offsetLeft, 50), findDivisible(e.pageY - map.offsetTop, 50), 50, 50);
mapContext.stroke();
});
map.addEventListener("click", function() {
mapContext.clearRect(0, 0, 500, 500);
})
</script>
You're not starting a new stroke for each rectangle, but "piling them up" so they get re-re-redrawn with .stroke().
Use .beginPath():
function findDivisible(x, scale) {
while (x % scale !== 0 && x > 0) {
x = x - 1;
}
return x;
}
var map = document.getElementById("myMap");
map.width = 300;
map.height = 300;
var mapContext = map.getContext("2d");
map.addEventListener("mousemove", function(e) {
mapContext.clearRect(0, 0, map.width, map.height);
mapContext.beginPath();
mapContext.rect(
findDivisible(e.clientX - map.offsetLeft, 50),
findDivisible(e.pageY - map.offsetTop, 50),
50,
50,
);
mapContext.stroke();
});
map.addEventListener("click", function() {
mapContext.clearRect(0, 0, 500, 500);
});
<canvas id="myMap" style="width: 300px;height: 300px;background-color: beige;"></canvas>
You can try this:
map.addEventListener("mousemove", function(e){
map.width = map.width;
mapContext.rect(findDivisible(e.clientX-map.offsetLeft,50) , findDivisible(e.pageY - map.offsetTop,50), 50, 50);
mapContext.stroke();
});
map.addEventListener("click", function(){
map.width = map.width;
});
One of the ways that is implicitly endorsed in the spec and often used in people’s apps to clear a canvas
https://dzone.com/articles/how-you-clear-your-html5

Display canvas shape always on top of another shape

I have two moving rectangles in my canvas, one is green and one is red. When they are on the same position and you can see only one of them, the red rectangle is always displayed while the green one simply is under the red one.
How can i accomplish that the green one is always displayed on top?
without any code it is hard to tell, but if you have the code for the green rectangle first it will be drawn first and the red will be drawn on top of it.
I think you should paint the green one AFTER the red one. so if your rectangles are in an array, you can iterate on it in reverse order
(function start() {
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth || $(window).width();
canvas.height = window.innerHeight || $(window).height();
const ctx = canvas.getContext('2d');
const draw = function(opts={color: 'yellow', x: 0, y: 0}) {
ctx.beginPath();
ctx.rect(opts.x, opts.y, 150, 100);
ctx.fillStyle = opts.color;
ctx.fill();
}
// COLUMN 1
draw({color: 'red',x: 20,y: 20});
draw({color: 'green',x: 40,y: 40});
// COLUMN 2
draw({color: 'green', x: 200, y: 20});
draw({color: 'red', x: 220, y: 40});
})();
html,
body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
background: #000;
}
canvas {
position: absolute;
left: 0;
top: 0;
z-index: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id='canvas'></canvas>

JointJS : deep:true and translate

I try to add several times the same element in my paper.
Then I created it thanks to embed and then I cloned it with deep:true.
My problem is that deep:true change some properties (I don't know which) but I'm now enable to apply a translation to it.
here is my code, that work without deep:true but doesn't with. (but clone a rect and not my combined element) :
var width=400, height=1000;
var graph = new joint.dia.Graph; //define a graph
var paper = new joint.dia.Paper({ //define a paper
el: $('#myholder'),
width: width,
height: height,
gridSize: 1,
model: graph, //include paper in my graph
restrictTranslate: function(element) { //restriction of drag n drop to the vertical line (x never changes)
return {
x: element.model.attributes.position.x, //this represent the original x position of my element
y: 0,
width: 0,
height: height
};
}
});
var rect1 = new joint.shapes.basic.Rect({ //create first rect (with input and output)
position: { x: 100, y: 20 },
size: { width: 90, height: 90 },
attrs: {
rect: { fill: '#2ECC71' }
}
});
var add_block= new joint.shapes.basic.Circle({ //create add button
position: { x: 170, y: 20 },
size: { width: 20, height: 20 },
attrs: {
circle: { fill: '#2ECC71'},
text:{text:'+', "font-size":20}
}
});
rect1.embed(add_block);
graph.addCell([rect1,add_block]);
var clones=rect1.clone({deep:true});
graph.addCells(clones);
var line = V('line', { x1: 250, y1: 10, x2: 250, y2: 500, stroke: '#ddd' });
V(paper.viewport).append(line);
//Create a new block and link between two blocks
//chek if click on + button
//if click on this button, create a new block and link between them
paper.on('cell:pointerclick', function(cellView, evt, x, y) {
var elementBelow = graph.get('cells').find(function(cell) {
if (!cell.get('parent')) return false;//not interested in parent
if (cell instanceof joint.dia.Link) return false; // Not interested in links.
if (cell.getBBox().containsPoint(g.point(x, y))) {
return true;
}
return false;
});
if(elementBelow) //if we start from a block, let's go!
{
rect=rect1.clone({deep:true});
graph.addCell(rect);
rect.translate(200, 0);
}
});

Charts.js: thin donut chart background

I want to create a donut chart with a thin gray background in it.
The only way I found to create it is adding a second donut chart to create the background.
Is there any way to do it simpler?
HTML:
<div class="chart-cont">
<canvas id="pointsUsed" height="200"></canvas>
<canvas id="pointsUsedBg" height="200"></canvas>
</div>
CSS:
.chart-cont {
position: relative;
}
#pointsUsed {
position: absolute;
top: 0;
left: 0;
z-index: 2;
}
#pointsUsedBg {
position: absolute;
top: 0;
left: 0;
transform: scale(0.96,0.96);
}
JavaScript:
var pointsUsed = [
{
value: 44250,
color: "#FF5F33",
},
{
value: 100000,
color: "transparent",
},
];
var pointsUsedBg = [
{
value: 100000,
color: "#EEEEEE",
},
];
var pointsUsed_ctx = document.getElementById("pointsUsed").getContext("2d");
var pointsUsedBg_ctx = document.getElementById("pointsUsedBg").getContext("2d");
var pointsUsed = new Chart(pointsUsed_ctx).Doughnut(pointsUsed, {
segmentShowStroke: false,
segmentStrokeWidth : 0,
percentageInnerCutout: 87,
showTooltips: false,
animationEasing: 'easeInOutCubic',
responsive: true
});
var pointsUsedBg = new Chart(pointsUsedBg_ctx).Doughnut(pointsUsedBg, {
segmentShowStroke: false,
segmentStrokeWidth : 0,
percentageInnerCutout: 94,
showTooltips: false,
animation: false,
responsive: true
});
JSFiddle
Thanks!
You can extend the Doughnut chart to do this, like so
Chart.types.Doughnut.extend({
name: "DoughnutAlt",
initialize: function (data) {
// call the actual initialize
Chart.types.Doughnut.prototype.initialize.apply(this, arguments);
// save the actual clear method
var originalClear = this.clear;
// override the clear method to draw the background after each clear
this.clear = function () {
// call the original clear method first
originalClear.apply(this, arguments)
var ctx = this.chart.ctx;
// use any one of the segments to get the inner and outer radius and center x and y
var firstSegment = this.segments[0];
// adjust 0.3 to increaase / decrease the width of the background
var gap = (firstSegment.outerRadius - firstSegment.innerRadius) * (1 - 0.3) / 2;
// draw the background
ctx.save();
ctx.fillStyle = "#EEE";
ctx.beginPath();
ctx.arc(firstSegment.x, firstSegment.y, firstSegment.outerRadius - gap, 0, 2 * Math.PI);
ctx.arc(firstSegment.x, firstSegment.y, firstSegment.innerRadius + gap, 0, 2 * Math.PI, true);
ctx.closePath();
ctx.fill();
ctx.restore();
}
}
});
You would call it like this
var pointsUsed = new Chart(pointsUsed_ctx).DoughnutAlt(pointsUsed, {
...
Fiddle - http://jsfiddle.net/7nfL1m7d/

Reset Array back to Original values

I'm working on a simple game with Javascript, involving changes to an HTML5 canvas. Have a number (10+) of objects entered in an array:
var boxes = [];
boxes.push({
x: 0,
y:canvas.height - 370,
width: 400,
height: 20});
boxes.push({
x: 300,
y:canvas.height - 100,
width: 150,
height: 90});
The entire array is then run through an update function, which changes the x value depending on the player's position:
for (var i = 0; i < boxes.length; i++) {
if (boxes[1].x > -3000 + canvas.width) {
boxes[i].x -= robot.speed * modifier;
robot.x = canvas.width - 300;
};
};
When the player dies a reset function is run:
var reset = function () {
robot.x = 0;
robot.y = 500;
++deaths;
};
I'm looking for a way to reset all the values of the boxes array to the original values when this function runs, essentially resetting the map, without having to enter each one manually, ie boxes[1].x = 300
https://jsfiddle.net/to31b612/
Simply initialize from a single function:
var boxes;
initBoxes();
function initBoxes() {
boxes = [];
boxes.push({
x: 0,
y:canvas.height - 370,
width: 400,
height: 20});
boxes.push({
x: 300,
y:canvas.height - 100,
width: 150,
height: 90});
}
Then just recall initBoxes() every time you need to initialize it.
You could just initialize boxes inside the reset() function, and make sure to call call reset() before the first run of the game, too.
var deaths = -1;
var boxes = [];
function reset() {
robot.x = 0;
robot.y = 500;
boxes = [{
x: 0,
y:canvas.height - 370,
width: 400,
height: 20
}, {
x: 300,
y:canvas.height - 100,
width: 150,
height: 90
}];
++deaths;
}
It sounds like you want to use the Memento Pattern
just use 2 variables
var initBoxes = [];
initBoxes.push({
x: 0,
y:canvas.height - 370,
width: 400,
height: 20});
initBoxes.push({
x: 300,
y:canvas.height - 100,
width: 150,
height: 90});
var boxes = initBoxes;
Then when ever you need to reset boxes just
boxes = initBoxes;

Categories

Resources