Douglas Crockfords - how to call base method into an inherited class - javascript

I'm trying to construct a base class Shape using Crockford's inheritance pattern. Using this base Shape, I'm trying to draw a circle, a rectangle and a triangle. I'm kinda stuck. I didn't know how to call/modify the base method
function points(x,y) {
x = this.x;
y = this.y;
}
function Shape() {
return {
this.points: [ ],
init : function(){
if(typeof this.context === ‘undefined’){
var canvas = document.getElementById(‘canvas’);
var context = canvas.getContext(‘2d’);
}
},
draw: function(){
var context = this.context;
context.beginPath();
context.moveTo(this.points[0].x, this.points[0].y);
for(var i=1; i< this.parameter.length; i++){
context.lineTo(this.parameter[i].x, this.parameter[i].y);
}
context.closePath();
context.stroke();
}
};
}
function Circle(x, y, r){
var points = Shape();
point.x = x;
points.y = y;
points.r = r;
var baseMethod = that.draw;
that.draw = function(){
/*how to modify the base method to draw circle*/
};
}
function Rectangle(a, b, c, d){
var points = Shape();
point.a = a;
points.b = b;
points.c = c;
points.d = d
var baseMethod = that.draw;
that.draw = function(){
/*how to call base method to draw rectangle*/
};
}

You've got quite a few problems going on with your code. Firstly you need to make sure you've got your basic drawing code working before moving on to more complicated shapes such as circles and rectangles. Start with drawing lines. I've tidied up your code and got it working with drawing straight lines:
//returns basic point object which has
//two properties x & y
function point(x, y) {
return {
x: x,
y: y
}
}
//function that returns a shape object with all the
//mechanisms for drawing lines between points
function Shape(canvasID) {
return {
points: [], //not 'this.points' (which would most likely be window.points)
addPoint: function(x, y) {//adding a point to a shape is an operation of shape
this.points.push(point(x, y))
},
init: function() {
if (typeof this.context === 'undefined') {
var canvas = document.getElementById(canvasID);
var ctx = canvas.getContext('2d');
this.context = ctx; //add the context reference to the current shape object
}
},
draw: function() {
this.init();
var context = this.context;
context.beginPath();
var that = this; //create a local reference to the current 'this' object.
//insures us against any possible 'this' scope problems
context.moveTo(that.points[0].x, that.points[0].y);
for (var i = 1; i < that.points.length; i++) {
context.lineTo(that.points[i].x, this.points[i].y);
}
context.closePath();
context.stroke();
}
};
}
//Simple Line object - good for testing your
//basic drawing functionality
function Line(canvasID, x, y, x2, y2) {
var shape = Shape(canvasID);
shape.addPoint(x, y);
shape.addPoint(x2, y2);
shape.draw();
}
//Execute your drawing functionality after the
//window has loaded to make sure all your objects exist before
//trying to use them
window.onload = function() {
Line('canvas', 100, 100, 200, 200);
}
I'm not necessarily sold on whether this is the best way to approach what you are doing - but DC's basic approach is to create objects without having to use the "new" keyword. So he returns an object from a function call using the JavaScript object notation.
Now that you can draw a line, the next step is to draw a series of connected lines one after the other (a path). After that, create your rectangle. You need some code to tell your code where to start drawing the rectangle (the start x/y coordinate) and then you can have parameters denoting the height and width of the rectangle which will be used to calculate the coordinates of the rectangle's corners and passed to the shape object to be drawn in the same way the series of connected lines were drawn. One caveat, though, is to check if there is some sort of 'createRectangle' function on the context object (and same for circle). I don't actually know myself as I've not done this sort of work in HTML5/canvas - although I have in other environments.
Edit
Forgot to mention that you will need to make sure the doctype declaration of your html is html5. A lot of IDE's will automatically declare your html as html4. Html5 just needs: <!DOCTYPE html>
Also, make sure you declare a canvas element in the html body, something like this:
<canvas id="canvas" width="300" height="150">
</canvas>

Related

Canvas JavaScript HTML --Creating several occurrences from one parent

I am initializing classes like so (Character is a class in my program):
character = new Character();
I want two instances of this class/object, so I tried this:
character2 = new Character();
Yet character2 simply replaces character; therefore, there is only one object. Is it possible to create another instance, or would I need to make another Character class (a lot of code duplication!).
I tried adding a second draw function (named draw2, for the second object), but that didn't help.
You need to move the definition of the Character.prototype.draw method out of the constructor function. Otherwise, each time you create a new Character(), you also override the Character.prototype.draw method.
You also need to replace references to local constructor function variables within the Character.prototype.draw method such as x, y or size with object properties such as this.X, this.Y and this.Size.
Also, you need to make the img a (possibly static) property of your Character.
Object creation in modern JS
Modern JS includes some shortcuts when defining objects. There is no real need to use prototype unless you are creating many instances of the Object. Accessing the prototype adds some overhead when using objects
You can create the object inside the creating function that allows you to define private properties via closure.
Closure creates private properties
function Character() {
var x = 0;
var y = 0;
var size = 25;
var vx = 4;
var vy = 4;
var width = 45;
var height = 45;
var img = new Image();
img.src = 'character.jpg';
var pattern;
// using API = {rather than return { allows you to access the instance of the
// inside this scope without having to use the `this` token
const API = {
get x() { return x },
get y() { return y },
get vx() { return vx },
get vy() { return vy },
get size() { return size },
get width() { return width },
get height() { return height },
set x(v) { x = v },
set y(v) { y = v },
set vx(v) { vx = v },
set vy(v) { vy = v },
set size(v) { size = v },
set width(v) { width = v },
set height(v) { height = v },
draw(ctx) {
ctx.save();
ctx.translate(x, y);
ctx.lineWidth = 2;
ctx.fillStyle = pattern ? pattern = ctx.createPattern(img, "no-repeat") : pattern;
ctx.beginPath();
ctx.moveTo(-size, -size);
ctx.lineTo(-size, size);
ctx.lineTo(size, size);
ctx.lineTo(size, -size);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
},
}
return API;
}
usage
var char = Character();
var char1 = Character();
// or
var char = new Character();
var char1 = new Character();
Performance considerations
If the object is required in performance code you may want to create more performant setters and getters, or avoid the getter setter overhead and include the properties in the object itself.
function Character() {
var img = new Image();
img.src = 'character.jpg';
var pattern;
// using API = {rather than return { allows you to access the instance of the
// inside this scope without having to use the `this` token
const API = {
x : 0,
y : 0,
size : 25,
vx : 4,
vy : 4,
width : 45,
height : 45,
draw(ctx) {
const size = API.size;
ctx.save();
ctx.translate(API.x, API.y); // Note that I use API.x rather than this.x
ctx.lineWidth = 2;
ctx.fillStyle = pattern ? pattern = ctx.createPattern(img, "no-repeat") : pattern;
ctx.beginPath();
ctx.moveTo(-size, -size);
ctx.lineTo(-size, size);
ctx.lineTo(size, size);
ctx.lineTo(size, -size);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
},
}
return API;
}
Notes.
I noticed you have way too many comments.
eg
//public property for VX
Object.defineProperty(this, 'width',
and
//function public draw method
Character.prototype.draw = function (ctx) {
//save the ctx
ctx.save();
//set x and y
ctx.translate(x, y);
//set the line width
ctx.lineWidth = 2;
You are stating the obvious in a comment, no machine will read it, no human needs to read it, so why is it there. Comments add noise, and source code noise is dangerous, avoid all unnecessary noise in your code.
You create the pattern each time the draw function is called, this is unneeded overhead. Create it only once.
There is a time when calling the draw function may not work as the image will not have loaded yet. Maybe you should manage images outside the object where you can ensure that media objects are loaded and ready to be used befor you try to use them.
Each instance of Character will load the image, create 100 of them and there will be 100 copies of the same image. This will affect performance and memory negatively.

JavaScript prototype inheritance and html canvas

I'm a Ruby developer who finally decided to learn JavaScript seriously. So I purchased some books and I started to dive in, but I got stuck quickly when I tried to understand prototypal inheritance...
One of the examples of the book is the following.
Given a Shape which prototype has a draw method, and two child shapes: a Triangle and a Rectangle which prototype inherit from Shape;
when I call the draw function on Triangle and Rectangle instances the method will draw them properly.
when I add a second method to show their name, every instance will log it properly.
Everything was understandable perfectly until I added a third method to fill the shapes... And only the last one get filled. no matter which one I call. Why? Is there something special in canvas?
Here is the code of the exercise:
function Point(x, y) {
this.x = x;
this.y = y;
}
function Shape() {
this.points = [];
this.init();
}
Shape.prototype = {
constructor: Shape,
init: function() {
if (this.context === undefined) {
Shape.prototype.context = document.getElementById('canvas').getContext('2d');
};
if (this.name === undefined) {
Shape.prototype.name = 'generic shape'
}
},
draw: function() {
var i, ctx = this.context;
ctx.strokeStyle = 'rgb(0,0,255)';
ctx.beginPath();
ctx.moveTo(this.points[0].x, this.points[0].y);
for (i = 1; i < this.points.length; i++) {
ctx.lineTo(this.points[i].x, this.points[i].y);
}
ctx.closePath();
ctx.stroke();
},
fill: function(color) {
var ctx = this.context;
ctx.fillStyle = color;
ctx.fill();
},
say_name: function() {
console.log('Hello my name is ' + this.name)
}
};
function Triangle(a, b, c) {
this.points = [a, b, c];
this.name = 'Triangle'
this.context = document.getElementById('canvas').getContext('2d');
}
function Rectangle(side_a, side_b) {
var p = new Point(200, 200);
this.points = [
p,
new Point(p.x + side_a, p.y), // top right
new Point(p.x + side_a, p.y + side_b), // bottom right
new Point(p.x, p.y + side_b) // bottom left
];
this.name = 'Rectangle'
this.context = document.getElementById('canvas').getContext('2d');
}
(function() {
var s = new Shape();
Triangle.prototype = s;
Rectangle.prototype = s;
})();
function testTriangle() {
var p1 = new Point(100, 100);
var p2 = new Point(300, 100);
var p3 = new Point(200, 0);
return new Triangle(p1, p2, p3);
}
function testRectangle() {
return new Rectangle(100, 100);
}
function make_me_crazy() {
var t = testTriangle();
var r = testRectangle();
t.draw();
r.draw();
t.say_name();
r.say_name();
t.fill('red');
}
make_me_crazy();
<canvas height='600' width='800' id='canvas' />
Thank you!
More details:
Why the function say_name is working exactly I expect saying: 'I am a triangle' or 'I am a rectangle' and never 'I am a generic shape', but the fill function fills the rectangle despite I'm calling it on a triangle instance? As people rightly answered to flip the two draw functions calls, I would specify better the following. The problem is not about the color of a shape, but the context pointer. why only the last shape is filled? If I add more shapes before calling fill only the last one get filled. This means I'm doing something wrong referring to the canvas. I supposed it was "the place where I draw shapes" but it seems more like "the last active shape"
How can I fix that code to make it working correctly filling the shape I want whenever I want? I mean. what if I want to have a function which receive an instance of a particular shape and fills it?
Is there any way to access a the draws contained into a canvas?
The core of the problem is the context - your shapes are sharing the single context of the canvas, and therefore it is not straight-forward to flip back and forth between objects. Instead, think of your order-of-operations as handling a single shape at a time and only moving on to the next one when you are done with the former.
Note the order of calls in the make_me_crazy function:
function Point(x, y) {
this.x = x;
this.y = y;
}
function Shape() {
this.points = [];
this.init();
}
Shape.prototype = {
constructor: Shape,
init: function(){
if (this.context === undefined) {
Shape.prototype.context = document.getElementById('canvas').getContext('2d');
};
if(this.name === undefined){
Shape.prototype.name = 'generic shape'
}
},
draw: function(){
var i, ctx = this.context;
ctx.strokeStyle = 'rgb(0,0,255)';
ctx.beginPath();
ctx.moveTo(this.points[0].x, this.points[0].y);
for (i = 1; i<this.points.length; i++) {
ctx.lineTo(this.points[i].x, this.points[i].y);
}
ctx.closePath();
ctx.stroke();
},
fill: function(color){
var ctx = this.context;
ctx.fillStyle = color;
ctx.fill();
},
say_name: function(){console.log('Hello my name is '+ this.name)}
};
function Triangle(a,b,c){
this.points = [a, b, c];
this.name = 'Triangle'
this.context = document.getElementById('canvas').getContext('2d');
}
function Rectangle(side_a, side_b){
var p = new Point(200, 200);
this.points = [
p,
new Point(p.x + side_a, p.y),// top right
new Point(p.x + side_a, p.y + side_b), // bottom right
new Point(p.x, p.y + side_b)// bottom left
];
this.name = 'Rectangle'
this.context = document.getElementById('canvas').getContext('2d');
}
(function(){
var s = new Shape();
Triangle.prototype = s;
Rectangle.prototype = s;
})();
function testTriangle(){
var p1 = new Point(100, 100);
var p2 = new Point(300, 100);
var p3 = new Point(200, 0);
return new Triangle(p1, p2, p3);
}
function testRectangle(){
return new Rectangle(100, 100);
}
function make_me_crazy(){
var t = testTriangle();
t.say_name();
t.draw();
t.fill('red');
var r = testRectangle();
r.draw();
r.say_name();
}
make_me_crazy();
<canvas height='600' width='800' id='canvas'></canvas>
About the points of your question.
For the first one: the key is this line of code
if(this.name === undefined){
Shape.prototype.name = 'generic shape'
}
When you instantiate Rectangle and Triangle, both of them set name.
In the other hand, the render method is only available in the Shape prototype.
About the second point (and the third one):
Maybe are you painting the Rectangle over the Triangle. Try to switch the order of the draw calls to check it.

Are there any shorter way to write codes for canvas html? I want to avoid using images if possible

How to simplify this code without repeating the context? Are there any library for canvas api?
I tried using with(context), but it throws an error because I am using 'use strict'. Are there any way around this?
context.save();
context.beginPath();
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(alien.x, alien.y);
context.rotate(0);
context.moveTo(-15, 0);
context.lineTo(-15, 5);
context.bezierCurveTo(-15, -10, 15, -10, 15, 5);
context.lineTo(15, 0);
context.lineTo(-15, 0);
context.lineTo(-15, 5);
context.lineTo(-20, 5);
context.lineTo(-25, 10);
context.lineTo(25, 10);
context.lineTo(20, 5);
context.lineTo(-20, 5);
context.moveTo(10, 10);
context.lineTo(10, 15);
context.lineTo(15, 15);
context.lineTo(15, 10);
context.moveTo(-10, 10);
context.lineTo(-10, 15);
context.lineTo(-15, 15);
context.lineTo(-15, 10);
context.strokeStyle = '#fff';
context.stroke();
context.closePath();
context.restore();
Write a wrapper object - with this you can also shorten the names as well as extend functionality like setting color and line width at the same time calling stroke and so on.
Update: I also made this library (free/MIT) which do wrapping of context.
Call them what you want - these are just examples of course. Make sure each one returns the this object which is what allows chaining. If you plan only to use a single instance you can put these methods inside the main object instead of using prototypes:
function CTX(ctx) {
this.ctx = ctx;
};
CTX.prototype.move = function(x,y) {this.ctx.moveTo(x, y); return this};
CTX.prototype.line = function(x,y) {this.ctx.lineTo(x, y); return this};
CTX.prototype.bez = function(a,b,c,d,e,f,g) {
this.ctx.bezierTo(a,b,c,d,e,f,g);
return this;
};
// etc.
To use: just wrap your context with this instance:
var ctx = new CTX(context);
ctx .save()
.begin()
.setTrans(1, 0, 0, 1, 0, 0)
.trans(alien.x, alien.y)
.rot(0)
.move(-15, 0)
.line(-15, 5)
.bez(-15, -10, 15, -10, 15, 5)
.line(15, 0)
// etc.
function CTX(ctx) {
this.ctx = ctx;
};
CTX.prototype.begin = function() {
this.ctx.beginPath();
return this;
};
CTX.prototype.move = function(x, y) {
this.ctx.moveTo(x, y);
return this;
};
CTX.prototype.line = function(x, y) {
this.ctx.lineTo(x, y);
return this;
};
CTX.prototype.stroke = function(color) {
if (color) this.ctx.strokeStyle = color;
this.ctx.stroke();
return this;
};
// ... define more here
var ctx = new CTX(canvas.getContext('2d'));
ctx
.begin()
.move(20, 20)
.line(50, 50)
.line(80, 20)
.line(110, 50)
.stroke('#00f');
// etc.
<canvas id=canvas width=500 height=180></canvas>
Here's one way to concisely specify your canvas path data using SVG-like syntax.
I yanked the following from an SVGPath-to-CanvasPath cross-compiler I wrote a while back. The nice thing about SVG path data is that it's very short & concise. It uses a single letter to define drawing command and it combines all the commands into a single string.
// Canvas path drawing commands represented by single command letters
M == moveTo
L == lineTo
Q == quadraticCurveTo
C == bezierCurveTo
R == rect
A == arc
Z == closePath
Y == clip
So your complex canvas path could be represented by a single short string like this:
var data='M-15,0 L-15,5 C-15,-10,15,-10,15,5 L15,0 L-15,0 L-15,5 L-20,5 L-25,10 L25,10 L20,5 L-20,5 M10,10 L10,15 L15,15 L15,10 M-10,10 L-10,15 L-15,15 L-15,10';
Here's example code and a Demo that parses & draws this data string to the Canvas:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var commandExpander={
M: 'moveTo',
L: 'lineTo',
Q: 'quadraticCurveTo',
C: 'bezierCurveTo',
R: 'rect',
A: 'arc',
Z: 'closePath',
Y: 'clip'
}
// your path written as a single string
var data='M-15,0 L-15,5 C-15,-10,15,-10,15,5 L15,0 L-15,0 L-15,5 L-20,5 L-25,10 L25,10 L20,5 L-20,5 M10,10 L10,15 L15,15 L15,10 M-10,10 L-10,15 L-15,15 L-15,10';
// convert the single data string into separate commands
var commands=parseData(data);
// execute the drawing commands
definePath(commands);
// stroke the result
ctx.stroke();
// Take a single string containing path data
// Break it into separate commands and put all commands into an array
// One command consists of a letter and the command arguments
function parseData(data){
var commands=[];
var cmdSegments = data.match(/[a-z][^a-z]*/ig);
for(var i=0;i<cmdSegments.length;i++){
var segment=cmdSegments[i];
var command={letter:null,args:null};
commands.push(command);
command.letter=segment.slice(0,1);
command.args=segment
.slice(1)
.trim()
.replace(/-+/g,' -')
.trim()
.replace(/\s+/g,',')
.replace(/,+/g,',')
.split(",")
.filter(function(e){return e!==''});
for(var a=0;a<command.args.length;a++){
command.args[a]=Number(command.args[a]);
}
}
return(commands);
}
// execute all the commands in the cmds[] array
function definePath(cmds){
ctx.beginPath();
for(var i=0;i<cmds.length;i++){
var command=commandExpander[cmds[i].letter];
var args=cmds[i].args;
ctx[command].apply(ctx,args);
}
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
Javascript does not have a default option for method chaining. Jquery does have this option, but not with the default Canvas methods (although there is a jCanvas plugin).
I think your best option is to write a method that executed a method with a set of arguments, which returns the context, so you can simulate chaining:
//Call on the canvas
//methodname is a string with the methodname;
//arguments is a list of arguments
canvas.prototype.applyMethodToContext = function(methodname, arguments){
this[methodname].apply(this, arguments);
return context;
}
This method is COMPLETELY untested and probably requires some changes to completely work. If you prefer to use a 3rd party solution which has been tested (and doesn't require entering the method as a string), someone has written a small javascript file (80 lines) that allows canvas chaining: https://github.com/millermedeiros/CanvasContext2DWrapper
Have a look at these site it just create a simple canvas for you http://www.w3resource.com/html5/canvas-element.php

Is it possible to move already drawn shapes on the html5 canvas?

note: i only want to move 1 shape at a time
Circle.prototype.create = function(){
if ( this.canvas === null ){
throw "Circle has no canvas";
}
if (this.canvas.getContext){
this.context = this.canvas.getContext("2d");
this.context.fillStyle = this.color;
this.context.beginPath();
this.context.arc(this.x,this.y,this.r,0,Math.PI*2);
this.context.closePath();
this.context.fill();
}
}
This draws a circle, Notice the context variable is saved as an object property
i've written this function to move this existing circle using the original circles context
Circle.prototype.move_to = function(x,y){
if (this.context === null){
throw "Circle has no context to move";
}
this.x = x; this.y = y;
this.context.translate(x, y);
this.context.beginPath();
this.context.arc(this.x,this.y,this.r,0,Math.PI*2);
this.context.closePath();
this.context.fill();
}
However this just draws yet ANOTHER circle.
How can i get it to move the existing?
WITHOUT CLEARING THE ORIGINAL AND DRAWING ANOTHER!
This is copied from another of my answers but the short answer is you can't do that. What you could do is store the information in an object like so.
var rectangle = {x:10,y:20,width:20,height:40};
Then you can change any of the values and redraw it, you have to clear the canvas first, or at least that portion of the canvas you are redrawing to.
//clear the canvas then draw
rectangle.width = 60;
ctx.fillRect(rectangle.x,rectangle.y,rectangle.width,rectangle.height);
Live Demo

How to create separate objects using Modular JavaScript pattern

I'm trying to update the interval value x but not succeeding. I hope to eventually have different pawn objects with internal values I can update when keypress up/down/left/right to redraw the canvas.
Code Update: Able to update x, y values now, but not sure about creating seperate objects using modular JavaScript pattern.
JavaScript using jQuery 1.5.1:
//Constructors
var pawn = (function() {
var x = 25;
var y = 25;
var getX = function() {
return x;
};
var getY = function() {
return y;
};
function _drawPawn(x,y) {
var x = x || 25;
var y = y || 25;
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var b = $('body');
var winH = b.height();
var winW = b.width();
$(canvas).attr('height',winH).attr('width',winW);
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.lineWidth="3";
ctx.arc(x, y, 10, 0, Math.PI * 2, true); // circle
ctx.stroke();
}
}
function left() {
x = 100;
y = 100;
}
return {
getX:getX,
getY:getY,
draw: function drawPawn(x,y) {
_drawPawn(x,y);
},
left:left
}
})();
//Init
$(function() {
var b = pawn;
b.left();
alert(b.getX());
var a = pawn;
alert(a.getX());
//b.draw();
});
and the html:
<canvas id="canvas" height="800px" width="600px">
Download a modern browser like Internet Explorer 9, Firefox, Safari or Chome to view this.
</canvas>
As it is currently written, your getX() function will be automatically invoked with the supplied (empty) parameter list, and return x, which is then equivalent to:
var getX = x;
which will give getX the value of x at the time it was declared.
To fix, removed the parentheses:
var getX = function() {
return x;
};
You also need to fix this function in your returned object:
draw: function drawPawn(x, y) {
_drawPawn(x, y);
}
Since the apparent intent is to call draw without parameters and use the currently bound x and y values, it should be:
draw: function drawPawn() {
_drawPawn(x, y);
}
First like alnitak said remove paranthese from getX, getY.
If u want different pawn objects you should use var b = new pawn()

Categories