I am trying to "extend" a DIV via Javascript by using a newly created div as prototype of my object.
As I understand Javascript, on creating a new instance of my Object via "new", the prototype-object is copied, assigned to "this" an then the function is executed (as the constructor).
Everything seems to work, except that whenever I create another object, and add it to the DOM, it "replaces" the original div. To be more exact: The constructor always changes the same div.
Using MyTest.prototype = document.createElement("div"); gives me the described behavior, the two commented lines after that in my code example are what I also tried, but to no avail.
I know trying to extend the DOM is frowned upon, but I want to understand this behavior, because I thought I knew how prototypes work and this simply does not fit my idea.
Here is a minimal example of what I am trying to do:
<!DOCTYPE html>
<html>
<head>
<title>Div-Prototype-Test</title>
<script type="text/javascript">
var height = 20;
var top = 0;
function MyTest() {
var r = Math.floor(Math.random() * 256);
var g = Math.floor(Math.random() * 256);
var b = Math.floor(Math.random() * 256);
this.style.backgroundColor = "rgb("+ r +","+ g +","+ b +")";
this.style.position = "absolute";
this.style.width = "500px";
this.style.height = height + "px";
this.style.top = top + "px";
top += height;
document.getElementsByTagName("body")[0].appendChild(this);
}
MyTest.prototype = document.createElement("div");
// MyTest.prototype = document.createElement("div").cloneNode(true);
// MyTest.prototype = new Element();
window.addEventListener(
"load",
function() {
var a = new MyTest();
var b = new MyTest();
var c = new MyTest();
var d = new MyTest();
}
);
</script>
</head>
<body>
</body>
</html>
PS: Because of a certain Javascript-Framework my search for anything that changes the prototype in Javascript always resulted in hundreds of results that had nothing to do with my problem - please tell me if I missed a question that already discusses this.
Edit:
To make my question clearer:
Here is an example where I use an object as prototype - its properties get copied.
function A() {
}
A.prototype = { property: 4 };
A.prototype.set = function(num) {
this.property = num;
}
window.addEventListener(
"load",
function() {
var message = "";
var x1 = new A();
message += "A1 : "+ x1.property +"\n";
x1.set(15);
message += "A1 : "+ x1.property +"\n";
var x2 = new A();
message += "A2 : "+ x2.property +"\n";
alert(message);
}
);
The alert then said:
A1 : 4
A1 : 15
A2 : 4
The Div in my first example however does not seem to be copied, it behaves like a Singleton or Monostate. Should it not go like this?
Protype object is copied into a new object
the new object is assigned to "this"
this is given to the constructor
this is returned by the constructor (if no return statement is specified)
MyTest.prototype = document.createElement("div");
This line is executed only once. It creates a MyTest.prototype object which is also a DOM element <div>. Every MyTest object will receive this same prototype. Therefore, every MyTest object you create will be associated with this single <div> you created only once. You will have to create a new <div> for every MyTest.
Try this pattern:
MyTest = function() {
var myDiv = document.createElement("div");
var r = Math.floor(Math.random() * 256);
var g = Math.floor(Math.random() * 256);
var b = Math.floor(Math.random() * 256);
myDiv.style.backgroundColor = "rgb("+ r +","+ g +","+ b +")";
myDiv.style.position = "absolute";
myDiv.style.width = "500px";
myDiv.style.height = height + "px";
myDiv.style.top = top + "px";
top += height;
document.getElementsByTagName("body")[0].appendChild(myDiv);
return myDiv;
}
This function creates a new <div>, using the createElement() call. Then, it sets all the properties you want on that new <div>. Finally, it returns your new <div>. As such, you can call it as
var myNewDiv = MyTest();
var myNewDiv = new MyTest();
Both options would work. In the second case a dummy new object is created by the new keyword, but it doesn't matter, as the new <div> created inside the function is actually returned.
You are mixing all kind of things. First, check my answer to this SO question. Second, Extending the Element Object can be done, but is is not supported by all browsers. Check this SO question.
Seems to me that you are planning to add elements to the document in some standardized way. Your code could be rewritten to (I modified it a bit):
function appendElement(content,id) {
var rgb = 'rgb('+ [Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256)].join(',') +')';
var top = Math.floor( Math.random() * 300 + 20 );
var left = Math.floor( Math.random() * 100 + 10 );
this.style.backgroundColor = rgb;
this.style.position = "absolute";
this.style.width = "500px";
this.style.height = "200px";
this.style.left = left+"px";
this.style.top = top+"px";
this.innerHTML = content || '';
this.id = id || Math.Random*10000+1
document.getElementsByTagName("body")[0].appendChild(this);
}
Now you can use this to append any element to the document using `appendElement as follows:
appendElement.call(document.createElement('div'),'empty div');
appendElement.call(document.createElement('span'),'new empty span','span1');
Would that be an idea for what you aim at?
I found a workaround, it basically works the other way round - the prototype is a blank object and I copy the new objects data into a div in the constructor:
var height = 20;
var top = 0;
function deepCopy(fromObject, toObject, depth) {
if(typeof(fromObject) != "object" || typeof(toObject) != "object") {
// throw "deepCopy only copies objects"
return;
}
if (typeof(depth) == "undefined") {
depth = 0;
} else if (depth > 100) {
// Recursion depth too high. Abort.
// throw "deepCopy recursion depth cap hit"
return;
}
for (var key in fromObject) {
if (typeof(fromObject[key]) == "object" && fromObject[key] != null) {
if (typeof(fromObject[key].nodeType) != "undefined") {
toObject[key] = fromObject[key].cloneNode(true);
} else {
if (typeof(toObject[key]) != "object") {
toObject[key] = {};
}
deepCopy(fromObject[key], toObject[key], depth + 1);
}
}
toObject[key] = fromObject[key];
}
}
function MyTest() {
// This is ugly...
var self = document.createElement("div");
deepCopy(MyTest.prototype, self);
var r = Math.floor(Math.random() * 256);
var g = Math.floor(Math.random() * 256);
var b = Math.floor(Math.random() * 256);
self.style.backgroundColor = "rgb("+ r +","+ g +","+ b +")";
self.style.position = "absolute";
self.style.width = "500px";
self.style.height = height + "px";
self.style.top = top + "px";
top += height;
document.getElementsByTagName("body")[0].appendChild(self);
return self;
}
MyTest.prototype = {};
// MyTest.prototype = document.createElement("div").cloneNode(true);
// MyTest.prototype = new Element();
window.addEventListener(
"load",
function() {
var a = new MyTest();
var b = new MyTest();
var c = new MyTest();
var d = new MyTest();
}
);
Although I have the feeling that my deepCopy-function is a rather inelegant (and possibly very buggy) way to perform the task, but the other way round with using cloneNode() did not work.
My original problem came from this: When the prototype is copied, all scalar values are copied, while all objects are simply referenced (like copying pointers, the pointer value is duplicated, but not the data it points to).
Hope this helps someone.
Related
I want to annotate a route on a map using vanilla javascript. To that end, I have a canvas element, a draw routine to 'connect' divs to points on the map, a closure to construct small divs, and another closure to make the divs draggable, so they can be more conveniently positioned over the map. In both closures, I'm having what I think is the same problem.
js & HTML to show the problem with the make-it-draggable closure, makeDraggable, are below. The outermost function declares a variable, dragThis. The dragstart handler assigns it value. onDragOver and onDrop use it. The debugger says dragThis is in the same makeDraggable closure at every use. I expected this approach to simplify the overall structure and efficiency of the code.
The problem: When the drag and drop handlers fire, dragThis doesn't have the value assigned in dragStart. Why?(???) (In fact, the value in the drag and drop handlers seems to be the id of the element in the first call to makedraggable by test.)
Associated questions:
I use the setData/getData methods of the dataTransfer object. For the life of me, i don't understand the first argument of those functions, 'format'. It seems to have nothing whatever to do with 'format' (save that it changes 'text' to 'text/plain' internally), and everything to do with identifying a datum. The value of 'format' can be just about any string. From the W3: *The API does not enforce the use of MIME types; other values can be used as well.* 'name' would seem to be a more appropriate, um, name. Am i missing something? Shouldn't ducks be called ducks? This hardly seems deserving of a separate question, but I'm curious and don't know how else to ask.
MDN says the event object's pageX, pageY "...include any portion of the page not currently visible." They do not seem to include non-visible portions of scrolled elements, so that seems false. What am i missing? (And does my pageXY function correctly calculate a position that does take those invisible bits properly into account? It seems to work, but my example may be too simple.)
Firefox and fiddle seem happy with my code. Chrome, though, at drop time in placeEl, thinks 'el' in 'el.style.left =...' is null: TypeError: Cannot read property 'style' of null.... Nonetheless, it is happy with the next line, and magically goes on to properly position the div.
I put the code at https://jsfiddle.net/HerbX/g7zv1ok2/1/ Maybe its still there. I've hardwired illustrative divs into the html.
Apparently having referenced the fiddle, I need to put the code here as well:
var MYSTUFF = {
makeDraggable:function(el, elChart) {
var dragThis = el;
var ops = {capture:true, useCapture:true};
el.draggable = true;
el.addEventListener('dragstart', onDragStart, ops);
console.log("dragstart listener added to " + el.id);
if (elChart.dataset.dragover === undefined) {
elChart.addEventListener('dragover', onDragOver, ops);
elChart.addEventListener('drop', onDrop, ops);
elChart.dataset.dragover = 'dragover';
console.log("dragover listener added to " + elChart.id);
}
return el;
function onDragStart(ev) {
var clickXY;
dragThis = ev.target;
clickXY = MYSTUFF.pageXY(ev);
ev.dataTransfer.clearData();
ev.dataTransfer.setData('text/plain',toStr(ev.target.id, ev.target.offsetLeft, ev.target.offsetTop, clickXY.x, clickXY.y));
ev.dataTransfer.setData('foo', ev.target.id);
console.log("dragStart: dragThis.id is " + dragThis.id + ", dT = " + ev.dataTransfer.getData('text/plain'));
}
function onDragOver(ev){
var pos; // new (style.top, style.left)
var canvasid; // canvas, if px, py exist
var params, el;
var foo;
ev.preventDefault();
params = ev.dataTransfer.getData('text/plain').split(';')
foo = ev.dataTransfer.getData('foo');
el = document.getElementById(params[0]);
pos = placeEl(ev, el); // Reposition el by delta(mouse)
console.log("onDragOver: dragThis.id = " + dragThis.id + '; foo = ' + foo);
}
function onDrop(ev) {
var canvasTemp, canvasid, ctx;
var dT, params;
var el, els;
ev.preventDefault();
dT = ev.dataTransfer.getData('text/plain');
params = dT.split(';');
console.log("onDrop event: dragThis.id is " + dragThis.id + ", dT is " + dT);
el = document.getElementById(params[0]);
placeEl(ev,el); //Reposition el, ignore return.
}
function toStr() {
// arguments => ;-delimited string. Args must be scalar numbers.
var delim='';
var s = "";
for (var i = 0; i < arguments.length; i++) {
if (isNaN(arguments[i])) {
s += delim + arguments[i];
} else {
s += delim + arguments[i].toFixed(1);
}
delim = ";";
}
return s;
}
function placeEl(ev,el) {
/* Re-position el by delta(mouse position) */
var params;
var dx, dy;
var pos;
var cursorXY;
params = ev.dataTransfer.getData('text/plain').split(';');
cursorXY = MYSTUFF.pageXY(ev);
dx = cursorXY.x - parseFloat(params[3]);
dy = cursorXY.y - parseFloat(params[4]);
pos = {x:parseFloat(params[1]) + dx, y:parseFloat(params[2]) + dy};
el.style.left = pos.x + 'px';
el.style.top = pos.y + 'px';
return pos;
}
},
reportXY: function(ev) {
let x, y, abs, sAbs;
let msg = document.getElementById('msg');
let el = ev.srcElement;
let id = el.id;
if (id === "") id = el.tagName;
x = event.pageX.toFixed(0);
y = event.pageY.toFixed(0);
abs = MYSTUFF.pageXY(ev);
sAbs = "(" + abs.x + "," + abs.y + ")";
msg.innerText = "In " + id + ", cursor # Page XY: (" + x + "," + y +"). Including scrolls, cursor # " + sAbs;
},
pageXY:function(ev) {
let x = ev.pageX;
let y = ev.pageY;
let scrollX, scrollY, tagName, el;
el = ev.srcElement;
tagName = el.tagName;
scrollX = el.scrollLeft;
scrollY = el.scrollTop;
while (tagName !== 'HTML') {
el = el.parentElement;
tagName = el.tagName;
scrollX += el.scrollLeft;
scrollY += el.scrollTop;
}
return {x:x+scrollX, y:y+scrollY}
}
}
/* test1() only tests makeDraggable. It uses elements hardwired into the HTML.*/
function test1() {
var elChart = document.getElementById('chartPosRef');
var div = document.getElementById('div-0');
MYSTUFF.makeDraggable(div, elChart);
div = document.getElementById('div-1');
MYSTUFF.makeDraggable(div,elChart);
div = document.getElementById('div-2');
MYSTUFF.makeDraggable(div,elChart);
div = document.getElementById('div-3');
MYSTUFF.makeDraggable(div,elChart);
}
window.addEventListener('DOMContentLoaded', (event) => {
window.addEventListener('mousemove', MYSTUFF.reportXY);
test1();
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>connected</title>
<style>
.small {
position:absolute;
width:7em;
background-color:#e6ffe6;
}
.red {
background-color:red;
}
</style>
<script src="js/Q1.js"></script>
</head>
<body>
<div id="chartCtnr" style="text-align:center;">
<div id="chartResz" class="resizeable" style="width:500px;height:500px;display:inline-block">
<div id="chartScroll" style="overflow:auto;height:100%;width:100%;display:inline-block;">
<div id="chartPosRef" class="freezer" style="position:relative">
<canvas id="connectedCanvas" class="red" width="3718" height="2614" title="Track">This to draw the connected track</canvas>
<div id='div-0' class='small' style='top:100px;left:100px;'>this is div-0</div>
<div id='div-1' class='small' style='top:120px;left:080px;'>this is div-1</div>
<div id='div-2' class='small' style='top:140px;left:100px;'>this is div-2</div>
<div id='div-3' class='small' style='top:160px;left:080px;'>this is div-3</div>
</div>
</div>
</div>
</div>
<div id='msg'>this is a message</div>
</body>
</html>
The problem was the 'closure' wasn't a closure. Now it is, after reading https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures a little more carefully, and this works. Note that dragStart (and a couple of other variables) are defined at the top of 'dragger' (as, in effect, are makeDraggable, onDragStart and so on.)
Next to do (but not here cuz i doubt that anyone cares...) is to add variables needed to draw lines connecting the divs to points on the map (non-existent here). Most are closure variables, and setting them will add little to no overhead to the drag operation - largely the point of this exercise.
setData('foo', target.id) remains because I'd still like to know why documentation refers to that first argument as a 'format'.
var MYSTUFF = {
dragger: function() {
var dragThis; // element being dragged
var clickXY; // click-position relative to ULC of scrolled area
var elStartXY; // Initial position of dragThis rel to ULC of scrolled area.
var canvas;
return makeDraggable;
function makeDraggable (el, elChart) {
var ops = {capture:true, useCapture:true};
dragThis = el;
el.draggable = true;
el.addEventListener('dragstart', onDragStart, ops);
if (elChart.dataset.dragover === undefined) {
elChart.addEventListener('dragover', onDragOver, ops);
elChart.addEventListener('drop', onDrop, ops);
elChart.dataset.dragover = 'dragover';
}
return el;
}
function onDragStart(ev) {
dragThis = ev.target;
clickXY = MYSTUFF.pageXY(ev);
elStartXY = {x:ev.target.offsetLeft, y:ev.target.offsetTop};
ev.dataTransfer.setData('foo', ev.target.id);
}
function onDragOver(ev){
var pos; // new (style.top, style.left)
var canvasid; // canvas, if px, py exist
var params, el;
var foo;
ev.preventDefault();
pos = placeEl(ev,dragThis);
}
function onDrop(ev) {
var canvasTemp, canvasid, ctx;
var dT, params;
var el, els;
ev.preventDefault();
placeEl(ev,dragThis);
}
function toStr() {
// arguments => ;-delimited string. Args must be scalar numbers.
var delim='';
var s = "";
for (var i = 0; i < arguments.length; i++) {
if (isNaN(arguments[i])) {
s += delim + arguments[i];
} else {
s += delim + arguments[i].toFixed(1);
}
delim = ";";
}
return s;
}
function placeEl(ev,el) {
/* Re-position el by delta(mouse position) */
var params;
var dx, dy;
var pos;
var cursorXY;
cursorXY = MYSTUFF.pageXY(ev);
dx = cursorXY.x - clickXY.x;
dy = cursorXY.y - clickXY.y;
pos = {x:elStartXY.x + dx, y:elStartXY.y + dy};
el.style.left = pos.x + 'px';
el.style.top = pos.y + 'px';
return pos;
}
}, // end 'dragger'
reportXY: function(ev) {
let x, y, abs, sAbs;
let msg = document.getElementById('msg');
let el = ev.srcElement;
let id = el.id;
if (id === "") id = el.tagName;
x = event.pageX.toFixed(0);
y = event.pageY.toFixed(0);
abs = MYSTUFF.pageXY(ev);
sAbs = "(" + abs.x + "," + abs.y + ")";
msg.innerText = "In " + id + ", cursor # Page XY: (" + x + "," + y +"). Including scrolls, cursor # " + sAbs;
},
pageXY:function(ev) {
let x = ev.pageX;
let y = ev.pageY;
let scrollX, scrollY, tagName, el;
el = ev.srcElement;
tagName = el.tagName;
scrollX = el.scrollLeft;
scrollY = el.scrollTop;
while (tagName !== 'HTML') {
el = el.parentElement;
tagName = el.tagName;
scrollX += el.scrollLeft;
scrollY += el.scrollTop;
}
return {x:x+scrollX, y:y+scrollY}
}
}
/* test1() only tests makeDraggable. It uses elements hardwired into the HTML.*/
function test1() {
md = MYSTUFF.dragger();
var elChart = document.getElementById('chartPosRef');
var div = document.getElementById('div-0');
md(div, elChart);
div = document.getElementById('div-1');
md(div, elChart);
div = document.getElementById('div-2');
md(div, elChart);
div = document.getElementById('div-3');
md(div, elChart);
}
window.addEventListener('DOMContentLoaded', (event) => {
window.addEventListener('mousemove', MYSTUFF.reportXY);
test1();
});
function showShape(){
var top=Math.random()*370;
var left=Math.random()*350;
var width=Math.random()*300;
var height=Math.random()*300;
start = new Date().getTime();
document.getElementById("shape").style.display = "block";
document.getElementById("shape").style.top=top + "px";
document.getElementById("shape").style.left=left + "px";
document.getElementById("shape").style.width=width+"px";
document.getElementById("shape").style.height=height+"px";
}
I want to reduce the redundancy.There is too many documentgetelement that I want to get rid of.
1) Cache your element
2) Create an randomise function that accepts a number and returns an integer.
3) Ensure your element has its position property set.
const shape = document.getElementById('shape');
function rnd(seed) {
return Math.round(Math.random() * seed);
}
function showShape(shape) {
var top = rnd(370);
var left = rnd(350);
var width = rnd(300);
var height = rnd(300);
shape.style.position = 'absolute';
shape.style.display = 'block';
shape.style.top = `${top}px`;
shape.style.left = `${left}px`;
shape.style.width = `${width}px`;
shape.style.height = `${height}px`;
}
showShape(shape);
#shape {
background-color: blue;
}
<div id="shape" />
Further documentation:
Template literals
It's called caching. Not only you will write less code, but also greatly improve performance.
const shapeElem = document.getElementById("shape");
function showShape(){
var top=Math.random()*370;
var left=Math.random()*350;
var width=Math.random()*300;
var height=Math.random()*300;
start = new Date().getTime();
shapeElem.style.display = "block";
shapeElem.style.top=top + "px";
shapeElem.style.left=left + "px";
shapeElem.style.width=width+"px";
shapeElem.style.height=height+"px";
}
I am doing a website wich has a lot of animations managed by JavaScript, when i started i just defined a function and some variables for the animation and repeat the process, like this. And a think is not the good way.
//BRIGHT ANIMATION
var frameWidth1 = 386;
var frameHeight1 = 100;
var spriteWidth1 = 20067;
var spriteHeight1 = 100;
var spriteElement1 = document.getElementById("bright");
var curPx1 = 0;
var ti1;
function animateSpriteB() {
spriteElement1.style.backgroundPosition = "-" + curPx1 + 'px 0px';
curPx1 = curPx1 + frameWidth1;
if (curPx1 >= spriteWidth1) {
curPx1 = 0;
}
ti1 = setTimeout(animateSpriteB, 70);
}
animateSpriteB();
// PAPIRO ANIMATION
var frameWidth = 56;
var frameHeight = 218;
var spriteWidth = 2016;
var spriteHeight = 218;
var spriteElement = document.getElementById("roll-off");
var curPx = 0;
var ti;
function animateSprite() {
spriteElement.style.backgroundPosition = "-" + curPx + 'px 0px';
curPx = curPx + frameWidth;
ti = setTimeout(animateSprite, 27.7);
if (curPx === spriteWidth) {
clearTimeout(ti);
}
}
function slideMask(){
var mask = $("#paper-mask");
var paper = $("#paper");
mask.animate({
width: 450
},{
duration: 1000,
complete: function(){
$("#paper-content").fadeIn();
}
});
}
var ti = setTimeout(function(){
animateSprite();
slideMask();
}, 3000);
So know, I decided to use a constructor to re use the same code and manage all the animations in the website. i came with Something like this:
// CONSTRUCTOR WHO MANAGE THE ANIMATIONS FOR THE WEBSITE
function SpriteAnimation(frameWidth, spriteWidth, spriteElement, isLoop){
this.frameWidth = frameWidth;
this.spriteWidth = spriteWidth;
this.spriteElement = spriteElement;
this.isLoop = isLoop;
this.curPx = 0;
this.ti;
}
SpriteAnimation.prototype.start = function(){
var selector = document.getElementById(this.spriteElement);
selector.style.backgroundPosition = "-" + this.curPx + "px 0px";
this.curPx = this.curPx + this.frameWidth;
this.ti = setTimeout(this.start, 2000);
if (this.curPx === this.spriteWidth){
clearTimeout(this.ti);
}
this.start();
}
var letter = new SpriteAnimation(935.4, 17774, "letter", true);
letter.start();
I am having problems in performance, every time i run the code my browser just crash i also think im not doing good the loop. So here comes my question: how can i do to manage the animations with an object constructor in wich i can pass parameters like if it is loop animation and the sprite parameters?... I appreciate the help you guys can bring me :)
#Tibos Your code has been from great help for me i just spent almost 4 hours trying to achieve this, and then yo came out and make it really easy, this is how my code looks now, i added another parameter: frame rate. so every animation can have a different frame rate. Also modified a bit the if statement because the animation was running untill the sprite dissapear and i need them to stay in the last frame, let me know if this is the correct form.
// CONSTRUCTOR WHO MANAGE THE ANIMATIONS FOR THE WEBSITE
function SpriteAnimation(frameWidth, spriteWidth, spriteElement, shouldLoop, frameRate){
this.frameWidth = frameWidth;
this.spriteWidth = spriteWidth;
this.selector = document.getElementById(spriteElement);
this.shouldLoop = shouldLoop ;
this.curPx = 0;
this.frameRate = frameRate;
this.ti;
}
SpriteAnimation.prototype.start = function(){
this.selector.style.backgroundPosition = "-" + this.curPx + "px 0px";
this.curPx += this.frameWidth;
if (this.curPx < (this.spriteWidth - this.frameWidth)){
setTimeout(this.start.bind(this), this.frameRate);
} else if (this.shouldLoop) {
this.curPx = 0;
this.start();
}
};
var letter = new SpriteAnimation(935.4, 17774, "letter", true, 60);
letter.start();
You have a few problems in your code, presented here in order of impact:
recursively calling start
losing the reference to this
clearing timeout as soon as it's set
unused variables
selecting the element at each iteration
Here is some better code (that works):
function SpriteAnimation(frameWidth, spriteWidth, spriteElement, shouldLoop){
this.frameWidth = frameWidth;
this.spriteWidth = spriteWidth;
this.selector = document.getElementById(spriteElement);
this.curPx = 0;
this.shouldLoop = shouldLoop;
}
SpriteAnimation.prototype.start = function(){
this.selector.style.backgroundPosition = "-" + this.curPx + "px 0px";
this.curPx += this.frameWidth;
if (this.curPx <= this.spriteWidth){
setTimeout(this.start.bind(this), 2000);
} else if (this.shouldLoop) {
this.curPx = 0;
this.start();
}
};
var letter = new SpriteAnimation(935.4, 17774, "letter", true);
letter.start();
DEMO: http://jsbin.com/oJIYoRU/1/edit
This function calls itself recursively with no base case. As soon as you invoke it you will lock the UI and overflow the call-stack.
SpriteAnimation.prototype.start = function(){
... some code ...
this.start();
}
I have two almost identically classes written in js. I would like to make one of them extend the other one, in order to have less code. I'm a novice in javascript and I need a little help to make this.
I'm posting the classes here. Can anybody help?
//============================================================================================================================================
//Class1==================================================================================================================================
//============================================================================================================================================
function Class1(config){
var targetObj;
var copycanvas = null;
var copy = null;
var outputcanvas = null;
var draw = null;
var direction = config.direction || "lr";
var TILE_WIDTH = config.tileWidth || 100;
var TILE_HEIGHT = config.tileHeight || 100;
var SOURCERECT = {x:0, y:0, width:0, height:0};
var interval;
var tiles2 = [];
var cols = 0;
var rows = 0;
createTiles = function(){
tiles = [];
tiles2 = [];
var y=0;
while(y < SOURCERECT.height){
var x=0;
cols = 0;
while(x < SOURCERECT.width){
cols++;
x += TILE_WIDTH;
}
rows++;
y += TILE_HEIGHT;
}
var i, j;
if (direction == "tl"){
for (i = 0; i < rows; i++)
for (j = 0; j < cols; j++){
x = j * TILE_WIDTH;
y = i * TILE_HEIGHT;
var tile = new Tile();
tile.imageX = x;
tile.imageY = y;
tiles2.push(tile);
}
}
arrangeSquares();
};
arrangeSquares = function(){
var i, j, k;
var M, N;
M = rows;
N = cols;
i = j = 0;
var cnt = 0;
for (i = 0; i < N + M - 1; i++)
for (j = 0; j <= i; j++)
if (j < M && (i - j) < N){
tiles.push(tiles2[j * N + (i - j)]);
}
}
processFrame = function(){
copycanvas.width = outputcanvas.width = targetObj.width;
copycanvas.height = outputcanvas.height = targetObj.height;
copy.drawImage(targetObj, 0, 0, targetObj.width, targetObj.height);
for(var i=0; i < tiles.length; i++) {
var tile = tiles[i];
tile.alpha += 0.05;
var TH = Math.max(0, Math.min(TILE_HEIGHT, targetObj.height - tile.imageY));
var TW = Math.max(0, Math.min(TILE_WIDTH, targetObj.width - tile.imageX));
draw.save();
draw.translate(tile.imageX, tile.imageY);
draw.globalAlpha = Math.max(0, tile.alpha);
draw.drawImage(copycanvas, tile.imageX, tile.imageY, TW, TH, 0, 0, TW, TH);
draw.restore();
}
var ok = true;
for (i = 0; i < tiles.length; i++) {
if (tiles[i].alpha < 1) {
ok = false;
break;
}
}
if (ok)
{
clearInterval(interval);
showComplete();
}
};
function showComplete() {
$target.trigger("showComplete");
$img.show();
$(copycanvas).remove();
$(outputcanvas).remove();
if ($hideTarget)
$hideTarget.hide();
};
this.hide = function(target) {
};
var $target = null;
var $img = null;
var $hideTarget = null;
this.show = function(target, hideTarget){
$target = $("#" + target).show();
align($target);
if (hideTarget != undefined) {
$target.before($hideTarget = $("#" + hideTarget).show());
align($hideTarget);
}
$img = $("#" + target + " > img").filter(":first").hide();
$("<canvas/>").attr("id", "sourcecopy")
.css("position", "absolute")
.appendTo($target)
.hide();
copycanvas = document.getElementById("sourcecopy");
copy = copycanvas.getContext('2d');
$("<canvas/>").attr("id", "output")
.css("position", "absolute")
.appendTo($target);
outputcanvas = document.getElementById("output");
draw = outputcanvas.getContext('2d');
targetObj = document.getElementById($img.attr("id"));
clearInterval(interval);
SOURCERECT = {x:0, y:0, width: targetObj.width, height: targetObj.height};
createTiles();
for(var i=0; i<tiles.length; i++){
var tile = tiles[i];
tile.alpha = 0 - (i * (2 / tiles.length));
}
var intervalDelay = (config.duration * 1000) / (40 + rows + cols);
interval = setInterval(function() { processFrame(); }, intervalDelay);
};
function Tile(){
this.alpha = 1;
this.imageX = 0;
this.imageY = 0;
};
};
//============================================================================================================================================
//Class2===================================================================================================================================
//============================================================================================================================================
function Class2(config){
var targetObj;
var copycanvas = null;
var copy = null;
var outputcanvas = null;
var draw = null;
var direction = config.direction || "lr";
var TILE_WIDTH = config.barWidth || 50;
var TILE_HEIGHT = 100;
var SOURCERECT = {x:0, y:0, width:0, height:0};
var interval;
var tiles = [];
createTiles = function(){
tiles = [];
var y=0;
while(y < SOURCERECT.height){
var x=0;
while(x < SOURCERECT.width){
var tile = new Tile();
tile.imageX = x;
tile.imageY = y;
tiles.push(tile);
x += TILE_WIDTH;
}
y += TILE_HEIGHT;
}
};
processFrame = function(){
copycanvas.width = outputcanvas.width = targetObj.width;
copycanvas.height = outputcanvas.height = targetObj.height;
copy.drawImage(targetObj, 0, 0, targetObj.width, targetObj.height);
for(var i=0; i < tiles.length; i++) {
var tile = tiles[i];
tile.alpha += 0.05;
var TH = Math.max(0, Math.min(TILE_HEIGHT, targetObj.height - tile.imageY));
var TW = Math.max(0, Math.min(TILE_WIDTH, targetObj.width - tile.imageX));
draw.save();
draw.translate(tile.imageX, tile.imageY);
draw.globalAlpha = Math.max(0, tile.alpha);
draw.drawImage(copycanvas, tile.imageX, tile.imageY, TW, TH, 0, 0, TW, TH);
draw.restore();
}
var ok = true;
for (i = 0; i < tiles.length; i++) {
if (tiles[i].alpha < 1) {
ok = false;
break;
}
}
if (ok)
{
clearInterval(interval);
showComplete();
}
};
function showComplete() {
$target.trigger("showComplete");
$img.show();
$(copycanvas).remove();
$(outputcanvas).remove();
if ($hideTarget)
$hideTarget.hide();
};
this.hide = function(target) {
};
var $target = null;
var $img = null;
var $hideTarget = null;
this.show = function(target, hideTarget){
$target = $("#" + target).show();
align($target);
if (hideTarget != undefined) {
$target.before($hideTarget = $("#" + hideTarget).show());
align($hideTarget);
}
$img = $("#" + target + " > img").filter(":first").hide();
$("<canvas/>").attr("id", "sourcecopy")
.css("position", "absolute")
.appendTo($target)
.hide();
copycanvas = document.getElementById("sourcecopy");
copy = copycanvas.getContext('2d');
$("<canvas/>").attr("id", "output")
.css("position", "absolute")
.appendTo($target);
outputcanvas = document.getElementById("output");
draw = outputcanvas.getContext('2d');
targetObj = document.getElementById($img.attr("id"));
clearInterval(interval);
if (direction == "tb" || direction == "bt")
{
TILE_WIDTH = targetObj.width;
TILE_HEIGHT = config.barWidth;
}
else
{
TILE_WIDTH = config.barWidth;
TILE_HEIGHT = targetObj.height;
}
SOURCERECT = {x:0, y:0, width: targetObj.width, height: targetObj.height};
createTiles();
if (direction == "lr" || direction == "tb")
{
for(var i=0; i<tiles.length; i++){
var tile = tiles[i];
tile.alpha = 0 - (i * (1 / tiles.length));
}
}
else
{
for(var i=tiles.length - 1; i >= 0 ; i--){
var tile = tiles[i];
tile.alpha = 0 - ((tiles.length - i - 1) * (2 / tiles.length));
}
}
var intervalDelay = (config.duration * 1000) / (40 + tiles.length);
interval = setInterval(function() { processFrame(); }, intervalDelay);
};
function Tile(){
this.alpha = 1;
this.imageX = 0;
this.imageY = 0;
};
};
Try declaring the class like this.
var theClass = function theClass() {
....
to extend this class you can use the prototype method:
theClass.prototype.newMethodName = function () {
....
You have a couple of choices. You can isolate the common functionality into a third object that Class1 and Class2 share (aggregation), or you can actually create a hierarchy of objects (inheritance). I'll talk about inheritance here.
JavaScript doesn't have classes, it's a prototypical language. An object instance is "backed" by a prototype object. If you ask the instance for a property it doesn't have (and functions are attached to objects as properties), the JavaScript interpreter checks the prototype behind the object to see if it has the property (and if not, the prototype behind that object, etc., etc.). This is how prototypical inheritance works.
JavaScript is an unusual prototypical language in that, until recently, there was no way to create an object and assign its prototype directly; you had to do it through constructor functions. If you're using class-based terminology, you're probably going to be more comfortable with constructor functions anyway. :-)
Here's a basic inheritance setup (this is not how I would actually do this, more on that below):
// Constructs an Vehicle instance
function Vehicle(owner) {
this.owner = owner;
}
// Who's this Vehicle's owner?
Vehicle.prototype.getOwner = function() {
return this.owner;
};
// Constructs a Car instance
function Car(owner) {
// Call super's initialization
Vehicle.call(this, owner);
// Our init
this.wheels = 4;
}
// Assign the object that will "back" all Car instances,
// then fix up the `constructor` property on it (otherwise
// `instanceof` breaks).
Car.prototype = new Vehicle();
Car.prototype.constructor = Car;
// A function that drives the car
Car.prototype.drive = function() {
};
Now we can use Car and get the features of Vehicle:
var c = new Car("T.J.");
alert(c.getOwner()); // "T.J.", retrived via Vehicle.prototype.getOwner
The above is a bit awkward and it has a couple of issues with when things happen that can be tricky. It also has the problem that most of the functions are anonymous, and I don't like anonymous functions (function names help your tools help you). It's also awkward to call your prototype's version of a function if you also have a copy of it (e.g., a "supercall" — not an uncommon operation with hierarchies). For that reason, you see a lot of "frameworks" for building hierarchies, usually using class-based terminology. Here's a list of some of them:
The Class feature of Prootype, a general-purpose JavaScript library
Dean Edwards' base2
John Resig's Simple JavaScript Inheritance (Resig being the person who created jQuery)
Er, um, mine — which as far as I know is being used by about three people. I did it because I had issues with decisions each of the above made. I will be updating it to not use class terminology (and actually releasing it as a tiny library, rather than just a blog post), because none of these adds classes to JavaScript, and acting as though they do misses the point of JavaScript prototypical model.
Of those four, I'd recommend Resig's or mine. Resig's uses function decompilation (calling toString on function instances, which has never been standardized and doesn't work on some platforms), but it works even if function decompilation doesn't work, it's just slightly less efficient in that case.
Before jumping on any of those, though, I encourage you to look at the true prototypical approach advocated by Douglas Crockford (of JSON fame, also a big wig at YUI). Crockford had a great deal of input on the latest version of ECMAScript, and some of his ideas (most notably Object.create) are now part of the latest standard and are finding their way into browsers. Using Object.create, you can directly assign a prototype to an object, without having a constructor function.
I prefer constructor functions (with my syntactic help) for places where I need inheritance, but Crockford's approach is valid, useful, and gaining popularity. It's something you should know about and understand, and then choose when or whether to use.
Hallo,
I have 3 Different function in Javascript, the first one replaces HTML Selectboxs width custom selectbox created with ULs.
and the other 2 replace Checkbox and Radio buttons respectivly.
Now I want to derive classes out of these functions, and need your suggestions, what will be the best way to organize these functions into class, whether inheretance is possible?
I really appriciate your help.
Thanks.
Here is some sample code.
function replaceSelect(formid) {
var form = $(formid);
if (!form) return;
invisibleSelectboes = document.getElementsByClassName("optionsDivInvisible");
if (invisibleSelectboes.length > 0) {
for (var i = 0; i < invisibleSelectboes.length; i++) {
document.body.removeChild(invisibleSelectboes[i]);
}
}
var selects = [];
var selectboxes = form.getElementsByTagName('select');
var selectText = "Bitte auswählen";
var selectRightSideWidth = 21;
var selectLeftSideWidth = 8;
selectAreaHeight = 21;
selectAreaOptionsOverlap = 2;
// Access all Selectboxes in Search mask.
for (var cfs = 0; cfs < selectboxes.length; cfs++) {
selects.push(selectboxes[cfs]);
}
// Replace the select boxes
for (var q = 0; q < selects.length; q++) {
if (selects[q].className == "") continue;
var onchangeEvent = selects[q].onchange;
//create and build div structure
var selectArea = document.createElement('div');
var left = document.createElement('div');
var right = document.createElement('div');
var center = document.createElement('div');
var button = document.createElement('a');
// var text = document.createTextNode(selectText);
var text = document.createTextNode('');
center.id = "mySelectText" + q;
if ( !! selects[q].getAttribute("selectWidth")) {
var selectWidth = parseInt(selects[q].getAttribute("selectWidth"));
} else {
var selectWidth = parseInt(selects[q].className.replace(/width_/g, ""));
}
center.style.width = selectWidth + 'px';
selectArea.style.width = selectWidth + selectRightSideWidth + selectLeftSideWidth + 'px';
if (selects[q].style.display == 'none' || selects[q].style.visibility == 'hidden') {
selectArea.style.display = 'none';
}
button.style.width = selectWidth + selectRightSideWidth + selectLeftSideWidth + 'px';
button.style.marginLeft = -selectWidth - selectLeftSideWidth + 'px';
// button.href = "javascript:toggleOptions( + q + ")";
Event.observe(button, 'click', function (q) {
return function (event) {
clickObserver(event, q)
}
}(q));
button.onkeydown = this.selectListener;
button.className = "selectButton"; //class used to check for mouseover
selectArea.className = "selectArea";
selectArea.id = "sarea" + q;
left.className = "left";
right.className = "right";
center.className = "center";
right.appendChild(button);
center.appendChild(text);
selectArea.appendChild(left);
selectArea.appendChild(right);
selectArea.appendChild(center);
//hide the select field
selects[q].style.display = 'none';
//insert select div
selects[q].parentNode.insertBefore(selectArea, selects[q]);
//build & place options div
var optionsDiv = document.createElement('div');
if (selects[q].getAttribute('width')) optionsDiv.style.width = selects[q].getAttribute('width') + 'px';
else optionsDiv.style.width = selectWidth + 8 + 'px';
optionsDiv.className = "optionsDivInvisible";
optionsDiv.id = "optionsDiv" + q;
optionsDiv.style.left = findPosX(selectArea) + 'px';
optionsDiv.style.top = findPosY(selectArea) + selectAreaHeight - selectAreaOptionsOverlap + 'px';
//get select's options and add to options div
for (var w = 0; w < selects[q].options.length; w++) {
var optionHolder = document.createElement('p');
if (selects[q].options[w].className == "informal") {
var optionLink = document.createElement('a');
var optionTxt = document.createTextNode(selects[q].options[w].getAttribute('text'));
optionLink.innerHTML = selects[q].options[w].getAttribute('text');
optionLink.className = "informal";
cic.addEvent(optionLink, 'click', function (event) {
Event.stop(event);
});
Event.observe(optionLink, 'mouseover', function (event) {
Event.stop(event);
});
Event.observe(optionLink, 'mouseout', function (event) {
Event.stop(event);
});
}
else {
var optionLink = document.createElement('a');
var optionTxt = document.createTextNode(selects[q].options[w].text);
optionLink.appendChild(optionTxt);
cic.addEvent(optionLink, 'click', function (id, w, q, onchangeEvent) {
return function () {
showOptions(q);
selectMe(selects[q].id, w, q, onchangeEvent);
}
}(selects[q].id, w, q, onchangeEvent));
}
//optionLink.href = "javascript:showOptions(" + q + "); selectMe('" + selects[q].id + "'," + w + "," + q + ");";
optionHolder.appendChild(optionLink);
optionsDiv.appendChild(optionHolder);
if (selects[q].options[w].selected) {
selectMe(selects[q].id, w, q);
}
}
document.getElementsByTagName("body")[0].appendChild(optionsDiv);
Event.observe(optionsDiv, 'mouseleave', function (submenuid) {
optionsDiv.className = 'optionsDivInvisible'
});
cic.addEvent(optionsDiv, 'click', function (event) {
if (event.stopPropagation) event.stopPropagation();
else event.cancelBubble = true;
});
}
form.setStyle({
visibility: 'visible'
});
}
From the sounds of it, you're looking to create a unified API to encapsulate all of this "form enhancing" functionality. Possibly something like this:
var formEnhancement = {
SelectBox: function(){ /* ... */ },
CheckBox: function(){ /* ... */ },
RadioButton: function(){ /* ... */ }
};
formEnhancement.SelectBox.prototype = { /* ... define methods ... */ };
// etc. (other prototypes)
// Call something:
var myEnhancedSelectBox = new formEnhancement.SelectBox(
document.getElementById('id-of-a-select-box')
);
Does this answer your query?
I'd go with
var Library = (function()
{
function _selectBox()
{
// stuff
}
function _checkBox()
{
// stuff
}
function _radioButton()
{
// stuff
}
return {
SelectBox : _selectBox,
CheckBox : _checkBox,
RadioButton : _radioButton
};
})();
or
var Library = (function()
{
return {
SelectBox : function()
{
// stuff
},
CheckBox : function()
{
// stuff
},
RadioButton : function()
{
// stuff
}
};
})();
[Edit]
this way, you can actually declare "private" variables that can be accessible only from the library itself, just declaring var foo="bar"; inside Library's declaration, makes a foo variable that can't be accessed from outside, but can be accessed by anything within Library, this is why functions like _selectBox in my example remain private, but can still be accessed through Library.SelectBox, which would be the "public getter"
[/Edit]
also, instead of
var Library = (function(){})();
you could do something like this:
var Library = Library || {};
Library.UI = (function(){})();
this way, you can keep separate parts of your code library, you can keep them in separate files, which don't care about the order in which they are loaded, as long as they have
var Library = Library || {};
on top of them
the functions would then be called like this:
Library.SelectBox();
or in the case you chose to go with "subclasses"
Library.UI.SelectBox();
All the answers are general patterns I think none of them is really helpful. Just because you put your 3 huge function into an object doesn't make your code modular, reusable, maintainable.
So my first suggestion is to utilize function decomposition. You've mentioned inheritance. Now if your code is basically made of this 3 giant functions nothing can be inherited or shared. You should separate function logic by purpose into smaller, more straighforward ones.
A good example is that you've mentioned the word replacing is relevant in all your cases. Maybe you can set up a function that is responsible for DOM replacement independently of the element's type. Such function can be shared between your modules making your code more robust and allowing you to DRY.
The best way to organize this process is called wishful thinking, when you solve your problem with functions which are intuitive and helpful even though they may not even exist. This is related to how you can design effective interaces.
Put the functions in a namespace:
Declare it like this:
FormUtils = {};
and add its properties, which will be your functions
FormUtils.replaceSelect = function () {/*your code*/};
FormUtils.replaceCheckbox = function () {/*your code*/};
FormUtils.replaceRadio = function () {/*your code*/};
then you call this functions with their namespace:
FormUtils.replaceSelect();
This is a simple and very accepted design pattern to javascript