I'm kinda newbie in JS objects, but I'd like to learn correct ways of doing things.
I'm writing a Class and it is mostly based on element that is passed to constructor. I want this element to have an event that updates the object.
I made it so it happens, but this just feels "hacky".
function SomeClass(element) {
this.element = element;
this.mouseX = 0;
this.mouseY = 0;
this.setMousePosition = function(posX, posY) {
this.mouseX = posX;
this.mouseY = posY;
};
this.listenMousePosition = function() {
var obj = this;
$(this.element).on('mousemove',null, {obj: obj}, function(e, obj) {
e.data.obj.setMousePosition(e.offsetX, e.offsetY);
$('#output').html('X: ' + e.data.obj.mouseX + '; Y: ' + e.data.obj.mouseY);
})
}
this.listenMousePosition();
return this;
}
window.contentDiv = new SomeClass($(".content")[0]);
Is there a better way of doing this or is this the way to roll?
As I checked - I can't just pass it to function as you see in link: https://jsfiddle.net/nooorz24/v6f1jydm/7/
At least a couple ways you could do this. The issue here is that when you get inside the function this changes from referencing the class instance to the method.
Reference another variable
By using an external variable, we don't care if this changes.
var self = this;
this.listenMousePosition = function() {
$(self.element).on('mousemove', function(e) {
self.setMousePosition(e.offsetX, e.offsetY);
$('#output').html('X: ' + self.mouseX + '; Y: ' + self.mouseY);
});
};
Use an arrow function
Arrow functions do not change what this references.
this.listenMousePosition = () => {
$(this.element).on('mousemove', e => {
this.setMousePosition(e.offsetX, e.offsetY);
$('#output').html('X: ' + this.mouseX + '; Y: ' + this.mouseY);
});
};
Take note though that arrow functions do not work in IE versions prior to Edge.
Related
I'm trying to generate a grid that responds to clicks and mouseover events. I have a tile manager object that holds tile objects, each of which have their own event listener. Currently, the actual method that gets called is in the manager object, though I would prefer they exist in the tile objects.
I would like to either find a solution more elegant than the one I currently know works, or at least understand why this solution even works at all when others do not:
for reference and clarity, cell refers to the javacript object and cell_node refers to a DOM object in the cell, additionally _handleClick and click_callback are the same function
Currently, to see if a callback happened, I have defined my callback as:
_handleClick(el,r,c,i){
console.log("element:" + el)
console.log("row:" + r)
console.log("col:" + c)
console.log("index:" + i)
}
Where _handleClick(el,r,c,i) is located in the manager object, not the tiles.
The addEventListener method looks like this:
cell_node.addEventListener('click',
(function(el,r,c,i){
return function(){
click_callback(el,r,c,i);
}
})(cell,r,c,i),false);
I cannot even begin to understand why this is necessary, or why it works when attempts such as:
cell_node.addEventListener('click', cell.some_clicked_function_with_no_arguments)
do not work. If I define cell.some_clicked_function_with_no_arguments with the this keyword, everything prints out as undefined, leaving me even more confused.
The two methods are posted in full below
class gridTile{
constructor(row, col, tile_id){
this.text= row + ", " + col;
this.child_text_div = this._make_child_text_div(this.text);
this.element = this._make_root_element()
this.row = row;
this.col = col;
this.id = tile_id;
}
_get_root_style(){
var randomColor = '#'+(Math.random() * 0xFFFFFF << 0).toString(16).padStart(6, '0');
return `
width: 100%;
padding-top: 100%;
//height: ${this.height}%;
background-color: ${randomColor};
position: relative;
`;
}
_make_child_text_div(text){
var cssobject =`
width: 100%;
height: 100%;
top: 0;
left: 0;
position: absolute;
`;
var obj = document.createElement("div");
obj.setAttribute("style", cssobject);
obj.innerHTML = text;
return obj;
}
_make_root_element(){
var root_grid_div = document.createElement("div");
root_grid_div.setAttribute("style", this._get_root_style());
root_grid_div.appendChild(this.child_text_div);
return root_grid_div;
}
get_element(){return this.element;}
clicked_callback(cell){
return function(cell){
console.log("element:" + cell.element);
console.log("row:" + cell.row);
console.log("col:" + cell.col);
console.log("index:" + cell.tile_id);
}
};
}
class Grid{
constructor(rows, columns){
this.grid_div = this._generate_grid_div(rows, columns, this._handleClick);
}
_generate_grid_div(rows, cols, click_callback){
// generate styling row and columns
var row_percent = String(1/rows * 100) + "% ";
var col_percent = String(1/cols * 100) + "% ";
console.log(row_percent + ", " + col_percent);
var rowstyle = "grid-template-rows: ";
for (var i=0; i<rows; i++){
rowstyle += row_percent
}
var colstyle = "grid-template-columns: ";
for (var i=0; i<cols; i++){
colstyle += col_percent
}
var style = `
display: grid;
${rowstyle};
${colstyle};
`
var grid = document.createElement('div');
grid.className = 'grid';
grid.setAttribute("style", style)
var i=0;
for (var r=0;r<rows;++r){
for (var c=0;c<cols;++c){
var cell = new gridTile(r, c, i);
var cell_node = grid.appendChild(cell.get_element())
cell_node.addEventListener('click',
(function(el,r,c,i){
return function(){
click_callback(el,r,c,i);
}
})(cell,r,c,i),false);
cell_node.addEventListener('mouseenter', click_callback(cell,r,c,i));
++i;
}
}
return grid;
}
_handleClick(el,r,c,i){
console.log("element:" + el)
console.log("row:" + r)
console.log("col:" + c)
console.log("index:" + i)
}
get_grid_element(){
return this.grid_div;
}
}
Wrapping the addEventListener() call in the IIFE instead might make it easier to read for you. As mentioned in comment above there isn't enough shown to know if this can be refactored to remove the IIFE
The following will act exactly the same:
(function(el, r, c, i) {
cell_node.addEventListener('click', function() {
click_callback(el, r, c, i);
}, false);
})(cell, r, c, i)
This ended up getting solved by adding an arrow function within a separate method:
_make_root_element(){
var root_grid_div = document.createElement("div");
root_grid_div.setAttribute("style", this._get_root_style());
root_grid_div.appendChild(this.child_text_div);
this._add_listeners_to_root_element(root_grid_div);
return root_grid_div;
}
get_element(){return this.element;}
_add_listeners_to_root_element(el){
el.addEventListener('click', () => this._clicked_callback());
}
_clicked_callback(){
console.log("element:" + this.element);
console.log("row:" + this.row);
console.log("col:" + this.col);
console.log("index:" + this.id);
};
This time, the this is not the element that called the event, but is now within the scope of the object the arrow function was defined in. This post helped me quite a bit in understanding what was going on, when other posts did not do so. This is more a reflection of my lack of ability to understand rather than the efforts of others to explain it to me.
I want to draw SVG path with mouse on canvas.
I don't want any library like rapheal.js here to draw shapes, I want pure JS.
I have creaed JS:
var svgCanvas = document.getElementById("svgCanvas");
var svgPath;
svgCanvas.addEventListener("touchstart", startDrawTouch, false);
svgCanvas.addEventListener("touchmove", continueDrawTouch, false);
svgCanvas.addEventListener("touchend", endDrawTouch, false);
function startDrawTouch(event)
{
var touch = event.changedTouches[0];
svgPath = createSvgElement("path");
svgPath.setAttribute("fill", "none");
svgPath.setAttribute("shape-rendering", "geometricPrecision");
svgPath.setAttribute("stroke-linejoin", "round");
svgPath.setAttribute("stroke", "#000000");
svgPath.setAttribute("d", "M" + touch.clientX + "," + touch.clientY);
svgCanvas.appendChild(svgPath);
}
function continueDrawTouch(event)
{
if (svgPath)
{
var touch = event.changedTouches[0];
var pathData = svgPath.getAttribute("d");
pathData = pathData + " L" + touch.clientX + "," + touch.clientY
svgPath.setAttribute("d", pathData);
}
}
function endDrawTouch(event)
{
if (svgPath)
{
var pathData = svgPath.getAttribute("d");
var touch = event.changedTouches[0];
pathData = pathData + " L" + touch.clientX + "," + touch.clientY
svgPath.setAttribute("d", pathData);
svgPath = null;
}
}
function createSvgElement(tagName)
{
return document.createElementNS("http://www.w3.org/2000/svg", tagName);
}
This take time on tablet to draw path. Having performance issue, in case you have better idea please share.
Thanks in advance.
You are reconstructing the path element in each continueDrawTouch call. That means converting it from the internal representation to a string then appending to the string and converting it back again.
Most browsers (Firefox for certain for instance) will be more performant if you avoid this and use the SVG DOM instead. The code would become:
if (svgPath)
{
var touch = event.changedTouches[0];
var newSegment = svgPath.createSVGPathSegLinetoAbs(touch.clientX, touch.clientY);
svgPath.pathSegList.appendItem(newSegment);
}
The same comment applies to the endDrawTouch function.
Maybe you can try if <polyline> and its .points property work and can give you better performance. Untested modification of your code:
var svgCanvas = document.getElementById("svgCanvas");
var svgPolyline;
svgCanvas.addEventListener("touchstart", startDrawTouch, false);
svgCanvas.addEventListener("touchmove", continueDrawTouch, false);
svgCanvas.addEventListener("touchend", endDrawTouch, false);
function startDrawTouch(event)
{
var touch = event.changedTouches[0];
svgPolyline = createSvgElement("polyline");
svgPolyline.setAttribute("fill", "none");
svgPolyline.setAttribute("shape-rendering", "geometricPrecision");
svgPolyline.setAttribute("stroke-linejoin", "round");
svgPolyline.setAttribute("stroke", "#000000");
svgCanvas.appendChild(svgPolyline);
continueDrawTouch(event);
}
function continueDrawTouch(event)
{
if (svgPolyline)
{
var touch = event.changedTouches[0];
var point = svgPolyline.ownerSVGElement.createSVGPoint();
point.x = touch.clientX;
point.y = touch.clientY;
var ctm = event.target.getScreenCTM();
if (ctm = ctm.inverse())
{
point = point.matrixTransform(ctm);
}
svgPolyline.points.appendItem(point);
}
}
function endDrawTouch(event)
{
continueDrawTouch(event);
svgPolyline = null;
}
function createSvgElement(tagName)
{
return document.createElementNS("http://www.w3.org/2000/svg", tagName);
}
Edit: .clientX/Y doesn't necessarily give you the coordinates you want, depending on the structure of your document, scroll or transformations. I therefore edited the code with some inspiration from another question (but using .screenX/Y, which should be more appropriate in connection with .getScreenCTM). The method name .getScreenCTM() caused me some confusion. .clientX/Y is indeed what's needed, see the specs.
this code works, but my question is I dont understand the purpose of var that = this. Why do I need to reference it like that to pass it to setInterval. I read about 'this' in http://www.sitepoint.com/what-is-this-in-javascript/, but it doesn't really answer my question
my JavaScript code
function spinClass(imageSource, height, width, forward, el){
this.src = imageSource;
this.spinFoward = forward;
this.element = document.getElementById(el);
this.height = height;
this.width = width;
this.d = 0;
var img = document.createElement("img");
img.setAttribute('src', this.src);
img.setAttribute('height', this.height);
img.setAttribute('width', this.width);
this.element.appendChild(img);
this.letSpin = function letSpin(){
//alert(this.d);
var that = this;
img.style.transform = "rotate(" + this.d + "deg)";
img.style.WebkitTransform= "rotate(" + this.d + "deg)";
img.style.MozTransform= "rotate(" + this.d + "deg)";
img.style.msTransform= "rotate(" + this.d + "deg)";
img.style.OTransform= "rotate(" + this.d + "deg)";
//alert(this.spinFoward);
if (this.spinFoward == true){
this.d++;
}else{
this.d--;
}
setInterval(function(){that.letSpin();}, 20);
};
}
The value of the this keyword is tied to the function it's used within and to how that function was called.
That includes both letSpin() and the short, anonymous function being passed to setTimeout(). And, the anonymous function won't automatically inherit or share the this value from letSpin() just by its placement.
So, you have to either capture the value in a variable with another name.
var that = this;
Or, bind the function so it will use a particular value when it's called.
setTimeout(function(){
this.letSpin();
}.bind(this), 20);
And, with bind, you can also pass the method without the anonymous function.
setTimeout(this.letSpin.bind(this), 20);
Instantiate object with this function:
function newClass(klass) {
var obj = new klass;
$.map(obj, function(value, key) {
if (typeof value == "function") {
obj[key] = value.bind(obj);
}
});
return obj;
}
This will do automatic binding of all function, so you will get object in habitual OOP style,
when methods inside objects has context of its object.
So you instantiate you objects not through the:
var obj = new spinClass();
But:
var obj = newClass(spinClass);
function Main(BombPosTopr, BompPosLeftr){
if (CheckRight == false){
//$("#Main").prepend('<div class="Effect" style="absolute; top:' + BombPosTopr + 'px; left: '+ BombPosLeftr +'px;"></div>');
ArrayEffects.push(new EffectVoorBom(BombPosTopr,BombPosLeftr));
BombPosLeftr += 30;
}
};
this.explosionTime2 = setTimeout( function(){
**self2.removeEffect();**
}
}
function EffectBom(BombPosTopr, BompPosLeftr){
var self2 = this;
this.el = $('<div/>');
this.el.addClass('Effect');
this.el.css({position : 'absolute', top : BombPosTopr + 'px', left : BompPosLeftr+'px'});
$("#Main").prepend(this.el);
self2.removeEffect = function(){
**self2.el.remove();**
}
I have 2 functions and in my main I need to add Effects, so I put them in an array and use the object EffectBom.
Now the big problem is that I need to use self2.removeEffect() in my other function but it can't find it!
Thnx for reading - helping!
You need to change the scope of the variable. Add
var self2;
to the top of the file and change
var self2 = this;
to be
self2 = this;
I used the following code to change the stroke-width when mouseover the path, but it doesn't work... I have checked many solutions on this matter, they seem to use the same solution as mine. My canvas is Raphael("svgContainer", 100, 100);
function drawPath(i,floorlevel,pointsNum){
var x1 = floorlevel[i].x;
var y1 = floorlevel[i].y;
var x2 = floorlevel[i+1].x;
var y2 = floorlevel[i+1].y;
var p = canvas.path("M"+x1 +" "+ y1);
p.attr("stroke", get_random_color());
p.attr("stroke-width",4);
p.attr("id",floorlevel[i].node+floorlevel[i+1].node);
p.animate({path:"M"+x1 +" "+ y1+ " L" + x2 +" "+ y2}, 1000);
var set = canvas.set();
var hoverIn = function() {
this.attr({"stroke-width": 10});
};
var hoverOut = function() {
this.attr({"stroke-width": 10});
}
p.hover(hoverIn, hoverOut, p, p);
set.push(p);
}
It seems to work fine when I sub in dummy values for the arguments you pass to the function:
http://jsfiddle.net/hKCDg/
I noticed you have the same stroke-width for hoverIn and hoverOut, which defeats the purpose.
var hoverIn = function() {
this.attr({"stroke-width": 10});
};
var hoverOut = function() {
this.attr({"stroke-width": 10});
};
I changed the latter to 5 in the demo here for visual effect.
Perhaps there's an error in the values you pass to the function?