I wanted to find out if there is any way to record all method calls(with arguments) and property access in Javascript.
For example:
1- I want to be informed when a canvas is being created by document.createElement("CANVAS");
2- I need to be informed when a script is trying to access navigator.plugins or window.screen.
Thank you in advance.
You can add hooks to functions, for example like this:
let calls = (function(){
let calls = 0;
let fun = document.createElement;
document.createElement = function(){
calls++;
return fun.apply(document, arguments);
}
return ()=>calls;
})();
let canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(200, 0, 0)';
ctx.fillRect(10, 10, 50, 50);
console.log("calls of document.createElement:", calls());
Small notice: don't try in a SO snippet to do a console.log from a hooked document.createElement as console.log itself is hooked by the snipped engine to build elements...
Another approach is to set proxies for object:
Object.defineProperty(window, "navigator", {
value: new Proxy(window.navigator, {
get: function(target, name) {
console.log("navigator property", name, "is read")
return target[name];
}
})
});
console.log("plugins:", window.navigator.plugins);
Related
I want to let a user drag and drop as many images as they wish onto an html5 canvas. From tutorials online I gather its something like:
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
imgs = arr.map(function callback(currentValue, index, array) {
img = img = document.createElement("img");
}[, thisArg])
mouseDown = false,
brushColor = "rgb(0, 0, 0)",
hasText = true,
clearCanvas = function () {
if (hasText) {
context.clearRect(0, 0, canvas.width, canvas.height);
hasText = false;
}
};
for (var i=0;i<imgs.length;i++){
imgs[i].addEventListener("load",function(){
context.drawImage(img,0,0);
})
}
I know line 4 is completely wrong..., but usually people seem to create their images as variables and then change the source with the dragged image. Since I want as many images as the user wishes to add, that would be an array without any size?
Furthermore, Since this code gets called once, on page load, this array cannot be appended to later (like when the user decides to add another image). So maybe this entire paradigm is incorrect in this case?
Can someone advise me on how to go about this? Thanks!
EDIT:
proposed solution:
imgs_arr = [];
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
imgs = imgs_arr.map(function callback(src) {
var img = document.createElement("img");
img.src = src;
return img;
});
mouseDown = false,
brushColor = "rgb(0, 0, 0)",
hasText = true,
clearCanvas = function () {
if (hasText) {
context.clearRect(0, 0, canvas.width, canvas.height);
hasText = false;
}
};
// Adding instructions
context.fillText("Drop an image onto the canvas", 240, 200);
context.fillText("Click a spot to set as brush color", 240, 220);
function drawImage(element,index,array){
element.addEventListener("load",function(){
clearCanvas()
context.drawImage(element,0,0)
})
}
imgs_arr.forEach(drawImage);
Currently throws : Cannot read property 'appendChild' of null(anonymous function) because my imgs_arr is blank - no one has added images yet.
Assuming your arr is an array of image URLs, your map should look like this:
imgs = arr.map(function callback(src) {
var img = document.createElement("img");
img.src = src;
return img;
});
I renamed currentValue to src to make sense as to what it is, and I removed the extra parameters, but that was all technically optional.
The important parts are:
Create a new img element and assign it to a variable.
Assign the URL to the src of that `img.
Return the image.
The map() function gathers up all the values returned from the callback and turns those into an array. That'll give you an array of Image objects (which are the JS form of the HTML <img> element).
The rest looks more or less correct (though you really should use ; instead of , for ending your lines to avoid possible weird things).
Also, for adding more after it is initialized, you should just have to push() a new Image onto the array and then redraw it.
I am creating a Mind Mapping software using raphael.js and javascript.
I want to create a set of nodes where a node is like a rectangle and has its attributes as id, x,y coordinates etc. There is a plus icon on the rectangle on click of which a new node/rectangle is created with its new unique id. I want to create node and assign each created node its id in an Object Oriented fashion.
I am having difficulty in assigning a unique id to each node on its creation. I want to set ids starting from 0,1,2,3...
Second difficulty is selecting a node. I want to select a node based on its id.
Please have a look at the following code.
Could someone please help me in assigning ids to each of the nodes and to select each node based on its id?
assume that there is a canvas of 1000px width and 700px height.
paper = Raphael("canvas", 1000,700);
// create.js
drawNode(290, 80);
function drawNode(x,y)
{
id=0;
a = new node(x,y,id);
a.drawNode();
}
// nodes.js
var node = function(x,y,id){
this.id=id;
this.x=x;
this.y=y;
this.drawNode = function(){
var st = paper.set();
a = paper.rect(this.x,this.y, 100, 40, 2);
a.add = paper.image('images/plus.png', this.x+77, this.y+12, 20, 20)
a.attr({fill: '#8EDFF0', stroke: '#6AB0F2'});
st.push(a,a.text,a.add,a.addnote);
a.add.click(this.add);
}
this.add = function () {
id=1;
a = new node(this.attrs.x+150, this.attrs.y,id);
a.drawNode();
}
}
Please tell me how can I set unique id to each node instead of hardcoding the values and how to do that.
You, probably, need some NodeCostructor singleton with id parameter set to 0 from the start and createNode as function that accepts x and y, uses NodeCostructor's id to draw a node and returns you a node, than increases NodeCostructor's id value. A basic example of how should it be (EDIT: I've found some js singleton tutorial, so this new code will be more valid in terms of OOP):
var NodeConstructor = (function() {
var instance = null;
function NCInstanceCreate() {
var id=0;
createNode = function(x,y) {
tmp = new node(x, y, this.id);
this.id++;
return tmp;
}
return {
createNode : createNode,
id : id
}
}
function getInstance() {
if( ! instance ) {
instance = new NCInstanceCreate();
}
return instance;
}
return {
getInstance : getInstance
};
})(window);
var node = function(x,y,id){
this.id=id;
this.x=x;
this.y=y;
}
myConstructor=NodeConstructor.getInstance();
console.log(myConstructor.createNode(100,200));
console.log(myConstructor.createNode(110,220));
console.log(myConstructor.createNode(130,240));
console.log(myConstructor.id);
So you basically should use myConstructor.createNode(x,y) instead of new node(x,y,id) whenever you want to create node.
(Using Firefox32, and Win7. But in other browsers I need it to work, too.)
I can't find a command to retrieve the content of the pattern-object that I set on the 2D-context.
Is there no direct way to get the value array and the width and height?
And if there is really no direct way, is there a workaround?
I could just use fillRect with the pattern on a hidden canvas and then reading out the canvas. But then, how to get the correct height and width?
Pattern properties
The only method exposed on the CanvasPattern object is to handle transformations:
interface CanvasPattern {
// opaque object
void setTransform(SVGMatrix transform);
};
This means all other properties has to be tracked externally.
Workaround 1 - manually keep track of properties
The workaround is to read the width and height from the image you used for the pattern, as well as mode and optionally transforms.
Just keep a reference to them for later:
var img = ...; // image source
var patternMode = "repeat"; // store repeat mode
var patternWidth = img.naturalWidth; // width and height of image
var patternHeight = img.naturalHeight; // = width and height of pattern
var pattern = ctx.createPattern(img, patternMode); // use to create pattern
Workaround 2 - create a custom object
You can create a custom object which wraps up the pattern creation process and exposes methods that can hold width, height etc.
Example
An object could look like this:
function PatternExt(ctx, image, mode) {
var ptn = ctx.createPattern(image, mode || "repeat");
this.setTransform = ptn.setTransform ? ptn.setTransform.bind(ptn) : null;
this.width = image.naturalWidth;
this.height = image.naturalHeight;
this.image = image;
this.mode = mode;
this.pattern = ptn;
}
Then it's just a matter of creating an instance almost the same way as with createPattern():
var p = new PatternExt(ctx, img, "repeat");
ctx.fillStyle = p.pattern;
To read information do:
var w = p.width;
var h = p.height;
...
Rename/extend as you want/need.
Demo for custom object
// load an image for pattern
var img = new Image();
img.onload = demo;
img.src = "http://i.imgur.com/HF5eJZS.gif";
function demo() {
var ctx = document.querySelector("canvas").getContext("2d"), p;
// create a pattern instance
p = new PatternExt(ctx, img, "repeat");
// use as fill-style
ctx.fillStyle = p.pattern;
ctx.fillRect(0, 0, 300, 150);
// show some properties
ctx.font = "24px sans-serif";
ctx.fillStyle = "#fff";
ctx.fillText([p.width, p.height, p.mode].join(), 10, 30);
}
function PatternExt(ctx, image, mode) {
var ptn = ctx.createPattern(image, mode || "repeat");
this.setTransform = ptn.setTransform ? ptn.setTransform.bind(ptn) : null;
this.width = image.naturalWidth;
this.height = image.naturalHeight;
this.image = image;
this.mode = mode;
this.pattern = ptn;
}
<canvas></canvas>
If your desired pattern is currently used as the fillStyle, then you can fetch it by fetching the fillStyle:
myPattern=context.fillStyle;
Otherwise you can't fetch your pattern object because the context keeps any pattern objects you've created as private properties.
So typically you keep a reference to your pattern until it's not needed anymore.
If you also need the original imageObject used to create your pattern then you typically save a reference to that image also.
// create an imageObject for use in your pattern
var myImageObject=new Image();
myImageObject.onload=start; // call start() when myImageObject is fully loaded
myImageObject.src="";
function start(){
// myImageObject has now been fully loaded so
// create your pattern and keep a reference to it
var myPattern = context.createPattern(myImageObject, 'repeat');
}
... and later when you need the pattern ...
// use your pattern object reference to apply the pattern as a fillStyle
context.fillStyle = myPattern;
... and later if you need the original image object
// get the original image object's size
var imgWidth=myImageObject.width;
var imgHeight=myImageObject.height;
// draw the original image object to the context -- or whatever you need it for
context.drawImage(myImageObject,50,50);
I want to enforce a miterLimit in actual pixels rather than as a ratio of the lineWidth. To do this, I'd like to hook any changes to lineWidth, and set the miterLimit simultaneously and automatically. I've used custom setters on objects before, but if I replace the lineWidth setter, I don't know of any way to actually pass the value to set on through to the actual canvas context.
Is there some way (compatible on IE9+) that I can listen to changes to a given key on an object without changing the behavior of setting that value?
Your getter/setter idea is a good one...
How about just adding a property definition to your context object?
Add a myLineWidth property to your context object and then set the linewidth using context.myLineWidth instead of context.lineWidth.
Some example code:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
Object.defineProperty(ctx, 'myLineWidth', {
get: function() {
return(this.lineWidth);
},
set: function(newWidth) {
this.lineWidth=newWidth;
console.log("Executed myLineWidth setter: ",this.lineWidth);
}
});
ctx.myLineWidth=5;
ctx.strokeRect(100,100,50,50);
Alternate Method using Encapsulation:
JavaScript does have true inheritance so it's not possible to inherit & override lineWidth.
The next best thing would be encapsulating the context object. Then all coders can use the encapsulated version of the context using the standard property and method syntax (no need for myLineWidth). If needed, here's a how-to: http://aboutcode.net/2011/10/04/efficient-encapsulation-of-javascript-objects.html.
I did a similar encapsulation in order to log the context drawings. Below, I've tried to snip the encapsulation code from one of my projects. You can ignore my special handling of drawImage and gradients as I needed to grab values from these that you won't need to grab--just add those methods to the returnMethods[] array.
Some example code for you to start with:
// Log all context drawings
// Creates a proxy class wrapping canvas context
function LoggedContext(canvas,context) {
var self = this;
this.canvas=canvas;
this.context=context;
this.imageURLs=[];
this.log=[];
this.gradients=[];
this.patterns=[];
this.init(self);
}
// maintain urls of images used
LoggedContext.prototype.imageIndex=function(url){
var i=a.indexOf(url);
// found
if(i>-1){ return(i); }
// not found -- added
a.push(url);
return(a.length-1);
}
///////////////////////////////////////////
// These methods require special handling
// (drawImage:need image.src, gradients:need gradDefs & colors)
//
LoggedContext.prototype.drawImage=function(){
this.context.drawImage.apply(this.context,arguments);
var args = Array.prototype.slice.call(arguments);
args[0]=arguments[0].src;
args.unshift(2,"drawImage");
var sArgs=JSON.stringify(args);
this.log.push(sArgs);
return(this);
}
//
LoggedContext.prototype.createLinearGradient =function(x1,y1,x2,y2){
var gradient=this.context.createLinearGradient(x1,y1,x2,y2);
gradient.context=this;
gradient.gradientID=this.gradients.length;
this.gradients.push({line:{x1:x1,y1:y1,x2:x2,y2:y2},stops:[]});
gradient.baseAddColorStop=gradient.addColorStop;
gradient.addColorStop=function(stop,color){
this.context.gradients[this.gradientID].stops.push({stop:stop,color:color});
this.baseAddColorStop(stop,color);
}
return(gradient);
}
//
LoggedContext.prototype.createPattern =function(i,r){
var pattern=this.context.createPattern(i,r);
pattern.patternID=this.patterns.length;
this.patterns.push({src:i.src,repeat:r});
return(pattern);
}
//
LoggedContext.prototype.createRadialGradient =function(sx,sy,sr,ex,ey,er){
var gradient=this.context.createRadialGradient(sx,sy,sr,ex,ey,er);
gradient.context=this;
gradient.gradientID=this.gradients.length;
this.gradients.push({circles:{sx:sx,sy:sy,sr:sr,ex:ex,ey:ey,er:er},stops:[]});
gradient.baseAddColorStop=gradient.addColorStop;
gradient.addColorStop=function(stop,color){
this.context.gradients[this.gradientID].stops.push({stop:stop,color:color});
this.baseAddColorStop(stop,color);
}
return(gradient);
}
// load the proxy object with all properties & methods of the context
LoggedContext.prototype.init=function(self){
// define public context properties
var properties={
//
fillStyle:"black",
strokeStyle:"black",
lineWidth:1,
font:"10px sans-serif",
//
globalAlpha:1.00,
globalCompositeOperation:"source-over",
//
shadowColor:"black",
shadowBlur:0,
shadowOffsetX:0,
shadowOffsetY:0,
//
lineCap:"butt", // butt,round,square
lineJoin:"miter", // miter,round,miter
miterLimit:10,
//
textAlign:"start",
textBaseLine:"alphabetic",
};
// encapsulate public properties
for (var i in properties) {
(function(i) {
if(!(i=="fillStyle")){
Object.defineProperty(self, i, {
get: function () {
return properties[i];
},
set: function (val) {
this.log.push(JSON.stringify([1,i,val]));
properties[i] = val;
this.context[i]=val;
}
})
}else{
Object.defineProperty(self, i, {
get: function () {
return properties[i];
},
set: function (val) {
if(typeof val ==="object"){
if(val.gradientID>=0){
this.log.push(JSON.stringify([1,i,"gradient",val.gradientID]));
}else if(val.patternID>=0){
this.log.push(JSON.stringify([1,i,"pattern",val.patternID]));
}
}else{
this.log.push(JSON.stringify([1,i,val]));
}
properties[i] = val;
this.context[i]=val;
}
})
}
})(i);
}
// define public context methods
var methods = ['arc','beginPath','bezierCurveTo','clearRect','clip',
'closePath','fill','fillRect','fillText','lineTo','moveTo',
'quadraticCurveTo','rect','restore','rotate','save','scale','setTransform',
'stroke','strokeRect','strokeText','transform','translate','putImageData'];
// encapsulate public methods
for (var i=0;i<methods.length;i++){
var m = methods[i];
this[m] = (function(m){
return function () {
this.context[m].apply(this.context, arguments);
// "arguments" is not a real array--so convert it
var args = Array.prototype.slice.call(arguments);
args.unshift(2,m);
var sArgs=JSON.stringify(args);
this.log.push(sArgs);
return(this);
};}(m));
}
// define context methods that return values
var returnMethods = ['measureText','getImageData','toDataURL',
'isPointInPath','isPointInStroke'];
// encapsulate return methods
for (var i=0;i<returnMethods.length;i++){
var m = returnMethods[i];
this[m] = (function(m){
return function () {
return(this.context[m].apply(this.context, arguments));
};}(m));
}
} // end init()
Hey I was wondering how I could add on to my function draw(); draw is used in my canvas engine to update everything within it. What I want to do is create a standalone engine that could say be left un-edited and yet update completely new things just linked to it. For example-
function draw(){
gameloop();
cameraWrapper();
context2D.clearRect(0,0,canvas.width, canvas.height);
}
Now say I create a new app and use this engine.. I want to be able to just create a standalone file linked to the engine say a player object.
player = new object();
function playerupdate(){
stuff;
stuff;
}
Now how could I say add the playerupdate() function into the engine.js's draw() function without editing the engine.js file? would this be like a prototype? if so and even if its not and example would be greatly appreciated! If you have any questions please ask, thanks in advance!
I think inheritance is a bit too complex for this... you can achieve all of what you want with just hooks.
Try something like this:
var postDrawHooks = [];
var draw = function(){
// do stuff
postDrawHooks.forEach(function(hook){hook()});
}
var playerUpdate = function(){...};
postDrawHooks.push(playerUpdate);
Basicaly it is a prototype. If you do not wish to complicate yourself with prototypeing you can use a "home made" inheritance:
Function.prototype.method = function(name, func) {
this.prototype[name] = func;
return this;
};
Function.method('inherits', function(parent) {
var d = {}, p = (this.prototype = new parent());
this.method('uber', function uber(name) {
if(!( name in d)) {
d[name] = 0;
}
var f, r, t = d[name], v = parent.prototype;
if(t) {
while(t) {
v = v.constructor.prototype;
t -= 1;
}
f = v[name];
} else {
f = p[name];
if(f == this[name]) {
f = v[name];
}
}
d[name] += 1;
r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
d[name] -= 1;
return r;
});
return this;
});
Now for a "class" (there is no such thing in js but this is the correct term ) you can make it inherit another "class" by using myCls.inherits(parentCls)
is there a reason you couldn't trigger events for these framework actions? that way anything listening for a 'draw' event could just hook their logic in that way? if not actual eventing, something like the hooks suggested by #sudhir jonathan would work, though i would suggest creating a method to register generic hooks, this way you could call something like
game.register('draw',myFunctionReference);
and in game object
register : function (hook,func) {
registeredHooks.push({'name' : hook, 'callback': func});
}
and in draw:
function draw(){
gameloop();
cameraWrapper();
context2D.clearRect(0,0,canvas.width, canvas.height);
for (i=0; i < registeredHooks.length; i++) {
var hook = registeredHooks[i];
if (hook.name == 'draw') hook.callback();
}
}