I'm trying to revive some old code I have which is a jigsaw puzzle. It loads images (puzzle pieces) from a folder, randomly places them around the page, and then you drag and drop onto the board. This used to work, but when I tried to use it today, it just throws errors (see below).
HTML:
<body onload="init();" onmousemove="initDrag();" onmouseup="mousePress=false;">
<div id="main">
<div id="inside">
<img src="holder.gif" style="position:absolute; left:500; top:500;" />
</div>
</div>
<div style="position:absolute;top:10px;left:600px;display:none;">
</div>
<p id="pText" class="congrats" style="position:absolute; top:410px; left:475px;"></p>
<p style="position:absolute; top:20px; left:750px;" class="links">hard puzzle | home</p>
</body>
Javascript:
// ARRAY FOR PLACEMENT OF IMAGES: [down, over]
var place = new Array([51,300],[51,432],[51,569],[51,707],[112,300],[112,432],[112,569],[112,707],[199,300],[199,432],[199,569],[199,707],[286,300],[286,432],[286,569],[286,707]);
var displayed = new Array();
var mousePress = false;
var pieces = place.length;
var placed = new Array();
var moveObject;
function init() {
var append = "";
for ( i=0; i<pieces; i++ )
{
append+= '<div onDrag="return false;" unselectable=on id="puzzle" name="puzzle" class="puzzleMain"><img src="img/us/img' + i + '.gif" onmousedown="mousePress=true;moveObject=' + i + ';if(document.all) {offsetX=window.event.offsetX;offsetY=window.event.offsetY;}" onmouseup="mousePress=false;doplace(' + i + ');"></div>';
displayed[i] = 0;
placed[i]=0;
}
document.getElementById("main").innerHTML += append;
document.images[i].onload = rndmImg;
}
function rndmImg() {
x=855;y=50;
do {
do {
displayNext = Math.floor( Math.random()* pieces );
} while( displayed[displayNext] );
document.getElementsByName("puzzle")[displayNext].style.top = y;
document.getElementsByName("puzzle")[displayNext].style.left = x;
document.getElementsByName("puzzle")[displayNext].style.visibility="visible";
displayed[displayNext]=1;
x += document.images[displayNext].width;
if( x >= 900 ) {
y += document.images[displayNext].height;
if ( y>=300 ) { x=0 } else { x=851 }
}
} while(!allDisplayed());
}
function allDisplayed() {
for( z=0; z<displayed.length; z++ ) if( !displayed[z] ) return false;
return true;
}
function lower() {
for( p=0;p<document.getElementsByName("puzzle").length;p++ )
document.getElementsByName("puzzle")[p].style.zIndex = 1;
document.getElementsByName("puzzle")[moveObject].style.zIndex=5;
}
function initDrag(e) {
if(!mousePress)return;
if( document.getElementById("inside").innerHTML != "" ) document.getElementById("inside").innerHTML = "";
lower();
if(document.all) {
mouseX = window.event.clientX - (offsetX);
mouseY = window.event.clientY - (offsetY);
} else {
mouseX = e.clientX - 50;
mouseY = e.clientY - 50;
}
document.getElementsByName("puzzle")[moveObject].style.top=mouseY;
document.getElementsByName("puzzle")[moveObject].style.left=mouseX;
return false;
}
function doplace(index) {
w = document.images[index].width;
h = document.images[index].height;
t = parseInt(document.getElementsByName("puzzle")[index].style.top);
l = parseInt(document.getElementsByName("puzzle")[index].style.left);
if ( ( l >= place[index][1]-(w/2) && l <= place[index][1]+(w/2) ) && ( t >= place[index][0]-(h/2) && t <= place[index][0] + (h/2) ) )
{
document.getElementsByName("puzzle")[index].style.top = place[index][0];
document.getElementsByName("puzzle")[index].style.left = place[index][1];
placed[index] = 1;
if(isComplete())
pText.innerHTML = "Puzzle is complete!";
}
}
function generateArray()
{
append="var place = new Array(";
for( i=0; i<document.getElementsByName("puzzle").length; i++ )
{
t = document.getElementsByName("puzzle")[i].style.pixelTop;
l = document.getElementsByName("puzzle")[i].style.pixelLeft;
append+="[" + t + "," + l + "],";
}
document.forms[0].mArray.value = append;
}
function isComplete()
{
for( q=0;q<placed.length;q++ ) if( !placed[q] )return false;
return true;
}
The error I mostly get is: Uncaught TypeError: Cannot read property 'clientX' of undefined
at initDrag (easy.html:70)
at HTMLBodyElement.onmousemove (easy.html:116)
I have a JSFiddle here. Can anyone shed some light on what the issue might be?
In your code you have onmousemove="initDrag();" so if document.all is falsy then mouseX = e.clientX - 50 would throw a Cannot read property 'clientX' of undefined because you call initDrag() without any argument on mouse move.
Now adays you neither should use document.all nor window.event, both are relicts.
Related
so I am relatively new to javascript. As a school project, we have to code a game with HTML, CSS, and Javascript.
So I want to make a cheap recreate of the game Sort or 'Splode' from New Super Mario Bros Ds.
I already programmed the random spawning of the bombs and animated the movement of the bombs. If they are too long in the middle sector (where they spawn) the explode.
But I didn't program the explosion yet. I want to program the drag and drop first. I just want to drag and drop a bomb. As simple as that, the rest I could do myself I guess.
I searched for a tutorial and found this webpage: https://html5.litten.com/how-to-drag-and-drop-on-an-html5-canvas/
I sort of recreated this in my game, but it is not working...
Sorry for my bad code. Could someone please explain to me how to do that?
Best,
Sebastian
enter image description here
<!DOCTYPE html>
<html>
<head>
<title>BOMB-Sorter</title>
<style>
canvas {
background-color:#fff;
border:7px solid #000;
border-radius: 5px;
display: inline;
background-image: url("img/background.jpg");
background-size: cover;
}
body{
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
class GraphObj {
constructor(x, y, w, h, c, x_vel, y_vel, t, d) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.c = c;
this.x_vel = x_vel;
this.y_vel = y_vel;
this.t = t;
this.d = d;
}
info() {
return "\ngraphical object: x="+this.x + ", y="+this.y+", w="+this.w+", h="+this.h+", h="+this.c;
}
}
// Declaration of Lists
var bList = null; // List of Bombs
var xlist = null;
var ylist = null;
var ref_time=0; // Reference Time
var lives = 10;
var score = 0;
var dragok = false;
var c = document.getElementById("myCanvas");
function drawGrid(ele,ctx){ // declaration of a new function
ctx.globalAlpha = 0.5;
ctx.fillStyle = "#ff9999";
ctx.fillRect(0,0,ele.width/3,ele.height)
ctx.fillStyle = "#60615a";
ctx.fillRect(2* (ele.width/3),0,ele.width/3,ele.height)
ctx.globalAlpha = 1;
ctx.setLineDash([25, 15]);
ctx.moveTo(ele.width/3 +1,0);
ctx.lineTo(ele.width/3 +1, ele.height);
ctx.moveTo(2*(ele.width/3 -1),0);
ctx.lineTo(2*(ele.width/3 - 1), ele.height);
ctx.lineWidth = 10;
ctx.strokeStyle = "#f5f242";
ctx.stroke();
}
function drawAll()
{
var ele = document.getElementById("myCanvas");
var ctx = ele.getContext("2d");
ele.width = ele.width; // clear everything!
drawGrid(ele,ctx); // call the function ...
var bombb = document.getElementById("bombb");
var bombr = document.getElementById("bombr");
var bombr_step = document.getElementById("bombr_step");
var bombb_step = document.getElementById("bombb_step");
var bombw = document.getElementById("bombw");
// draw bombs
for (var i=0; i<bList.length; i++)
{
var o = bList[i];
if(o.c == 0 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombr, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.c == 1 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombb, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.c == 2 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombr_step, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.c == 3 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombb_step, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.t == 10 || o.t == 8){
ctx.drawImage(bombw, o.x,o.y,o.w,o.h);
draggable: true;
}
}
// draw lives
ctx.font = "normal small-caps 70px Minecraft";
ctx.fillStyle = "red";
ctx.fillText(+lives, ele.width - 105, 70);
// draw score
ctx.font = "normal small-caps 70px Minecraft";
ctx.fillStyle = "black";
ctx.fillText(+score, 20, 70);
}
function positionCheck(i, ele){
if(bList[i].x < ele.width/3){
bList[i].x_vel = bList[i].x_vel * (-1);
}
else if(bList[i].x > 2*(ele.width/3) -64){
bList[i].x_vel = bList[i].x_vel * (-1);
}
else if(bList[i].y < 0){
bList[i].y_vel = bList[i].y_vel * (-1);
}
else if(bList[i].y > ele.height -64){
bList[i].y_vel = bList[i].y_vel * (-1);
}
}
function animateStep(){
for(var i = 0;i < bList.length; i++){
if(bList[i].c == 0){
bList[i].c = 2;
bList[i].t++;
}
else if(bList[i].c == 1){
bList[i].c = 3;
bList[i].t++;
}
else if(bList[i].c == 2){
bList[i].c = 0;
}
else if(bList[i].c == 3){
bList[i].c = 1;
}
else if(bList[i].t == 8){
bList[i].c = 4;
}
else if(bList[i].t == 10){
bList[i].c = 4;
}
}
}
function functimer(){
var ele = document.getElementById("myCanvas");
// create new bomb every s
if (Date.now() - ref_time >= 1000)
{
// random number between 0 and ele.width
var pos_x = Math.floor(Math.random() * (ele.width/3 - 64)) + (ele.width/3 +1);
//random height
var pos_y = Math.floor(Math.random() * (ele.height - 70) -1);
//random color
var color = Math.round(Math.random());
//random x velocity
var x_vel = Math.ceil(Math.random() * 2) * (Math.round(Math.random()) ? 1 : -1);
//random y velocity
var y_vel = Math.ceil(Math.random() * 2) * (Math.round(Math.random()) ? 1 : -1);
var t = 0;
var o = new GraphObj(pos_x,pos_y,64,64, color, x_vel, y_vel, t); // create new bomb
bList.push(o);
animateStep();
ref_time = Date.now();
}
var xlist = null;
xlist = new Array();
var ylist = null;
ylist = new Array();
// move bombs
for (var i=0; i<bList.length; i++)
{
bList[i].y += bList[i].y_vel;
bList[i].x += bList[i].x_vel;
positionCheck(i, ele);
}
drawAll();
requestAnimationFrame(functimer); // draw everything BEFORE screen update
}
function myMove(i,e){
if(dragok == true){
bList[i].x = e.pageX - c.offsetLeft;
bList[i].y = e.pageY - c.offsetTop;
}
}
function myDown(e){
for(var i = 0; i < bList.lenght; i++){
if(e.pageX < bList[i].x + 64 + c.offsetLeft && e.pageX > bList[i].x + c.offsetLeft && e.pageY < bList[i].y + 64 + c.offsetTop && e.pageY > bList[i].y + c.offsetTop){
bList[i].x = e.pageX - c.offsetLeft;
bList[i].y = e.pageY - c.offsetTop;
bList[i].x_vele = 0;
bList[i].y_vel = 0;
dragok = true;
c.onmousemove = myMove(i,e);
}
}
}
function myUp(){
dragok = false;
}
function start() {
// create lists
bList = new Array();
var c = document.getElementById("myCanvas");
c.setAttribute('tabindex','0');
c.focus();
c.onmousedown = myDown;
c.onmouseup = myUp;
var xlist = null;
xlist = new Array();
var ylist = null;
ylist = new Array();
drawAll();
requestAnimationFrame(functimer); // draw everything BEFORE screen update
}
</script>
</head>
<body onload="start()">
<img id="bombb" src="img/bombb.png" alt="x" style="display: none;">
<img id="bombr" src="img/bombr.png" alt="y" style="display: none;">
<img id="bombr_step" src="img/bombr_step1.png" alt="y" style="display: none;">
<img id="bombb_step" src="img/bombb_step.png" alt="y" style="display: none;">
<img id="bombw" src="img/bombw.png" alt="y" style="display: none;">
<div>
<canvas id="myCanvas" width="1000" height="600"></canvas>
</div>
</body>
</html>
This is not a total answer because I do not know exactly what is required when dragging a bomb, but here are a few places to make changes to help further debugging:
the canvas element 'c' is declared in two places so when it comes to be used on mousedown etc it is undefined, the one with more local scope having been set up in the start function. I suggest removing the 'var' before the use of c in the start function so you pick up the one that has global scope.
In general, go through the code seeing where things are defined and if that is in the best position - look at the scope of all variables. The code in this respect is almost OK, for example you understand you cant draw an img on a canvas until it is downloaded, but a few things could do with tidying up.
There is a misspelling of 'length' (lenght) which means a function won't work.
In general use your browser's dev tools console to ensure you do not have anything undeclared or looking like null etc. Such Javascript errors will show there. Also use console.log at strategic points to make sure you understand the flow of the logic. Starting with console.log('at function xxx') at the beginning of the main functions might be useful. You can then see the order in which mousedown/up might happen on a bomb for example.
If you could describe further what should happen when a bomb is dragged that would be useful. Just for completeness here I reiterate what I said in a comment that the bombs are going to be treated like rectangles - so obscuring bombs behind them from the point of view of dragging - that may or may not matter to your application. There is a way round it if it is important, but it's probably better to get the basics sorted out first.
This code allows me to click on a thumbnail and see the original in a new window.
Clicking outside of the window does an autoclose.
So far so good.
However if the image is larger than the screen resolution I have to doubleclick on the Title Bar and then right click and "view image" to make it full screen. How can I change the code to prevent the image going off the screen?
<script language="JavaScript" type="text/javascript">
PositionX = 10;
PositionY = 10;
defaultWidth = 600;
defaultHeight = 400;
var AutoClose = true;
function popImage(imageURL,imageTitle){
var imgWin = window.open('','_blank','scrollbars=no,resizable=1,width='+defaultWidth+',height='+defaultHeight+',left='+PositionX+',top='+PositionY);
if( !imgWin ) { return true; } //popup blockers should not cause errors
imgWin.document.write('<html><head><title>'+imageTitle+'<\/title><script type="text\/javascript">\n'+
'function resizeWinTo() {\n'+
'if( !document.images.length ) { document.images[0] = document.layers[0].images[0]; }'+
'var oH = document.images[0].height, oW = document.images[0].width;\n'+
'if( !oH || window.doneAlready ) { return; }\n'+ //in case images are disabled
'window.doneAlready = true;\n'+ //for Safari and Opera
'var x = window; x.resizeTo( oW + 200, oH + 200 );\n'+
'var myW = 0, myH = 0, d = x.document.documentElement, b = x.document.body;\n'+
'if( x.innerWidth ) { myW = x.innerWidth; myH = x.innerHeight; }\n'+
'else if( d && d.clientWidth ) { myW = d.clientWidth; myH = d.clientHeight; }\n'+
'else if( b && b.clientWidth ) { myW = b.clientWidth; myH = b.clientHeight; }\n'+
'if( window.opera && !document.childNodes ) { myW += 16; }\n'+
'x.resizeTo( oW = oW + ( ( oW + 200 ) - myW ), oH = oH + ( (oH + 200 ) - myH ) );\n'+
'var scW = screen.availWidth ? screen.availWidth : screen.width;\n'+
'var scH = screen.availHeight ? screen.availHeight : screen.height;\n'+
'if( !window.opera ) { x.moveTo(Math.round((scW-oW)/2),Math.round((scH-oH)/2)); }\n'+
'}\n'+
'<\/script>'+
'<\/head><body onload="resizeWinTo();"'+(AutoClose?' onblur="self.close();"':'')+'>'+
(document.layers?('<layer left="0" top="0">'):('<div style="position:absolute;left:0px;top:0px;display:table;">'))+
'<img src='+imageURL+' alt="Loading image ..." title="" onload="resizeWinTo();">'+
(document.layers?'<\/layer>':'<\/div>')+'<\/body><\/html>');
imgWin.document.close();
if( imgWin.focus ) { imgWin.focus(); }
return false;
}
</script>
<a href="./mypic.jpg" onclick="return popImage(this.href,'Magnify');">
<img src="./mythumb.jpg" width="500" height="333" border="0"></a>
I have a scenario in which I am having a canvas, I am dragging some drawing from the left side of the canvas and dropping it on the right side of it and if I am dropping one box near to another one it also clubbing together. Now I need to do two more things -
First, as soon as I am clubbing one with another I want to give this set, of more than one boxes, a treatment of one box. meaning that if I am dragging any one of the box which are clubbed together, whole set of the boxes should be dragged together.
second, as and when I am dropping the boxes I want to generate some plain text based on the box which has been dropped, for example if I dropped "first box", the plain text should be something like "you dropped first box first". And this thing I need to do on the sequence, meaning if the second box is appearing first the text according to that box should appear first.
Here is the code which I have completed
<script type="text/javascript">
window.onload = function(){
draw();
}
</script>
<body style="margin: 0;padding:0;clear:both; cursor: pointer">
<canvas id="canvas" tabindex="1" style="float:left" ></canvas>
<div id="plainEnglish" tabindex="2" style="float: left;"></div>
</body>
<script>
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
c.width = 600;
c.height = 300;
//My mouse coordinates
var x,y;
c.addEventListener("mousedown",down);
c.addEventListener("mousemove",move);
c.addEventListener("mouseup",up);
var r = 0;
function counter() {
r++;
console.log(r);
}
//I'll save my boxes in this array
var myBoxes = new Array();
// Those boxes which I have moved to droppable area of the canvas.
var myDroppedBoxes = new Array();
//This function describes what a box is.
//Each created box gets its own values
function box(x,y,w,h,rgb,text) {
this.x = x,
this.y = y;
this.xS = x; //saving x
this.yS = y; //saving y
this.w = w;
this.h = h;
this.rgb = rgb;
this.text = text;
//to determine if the box is being draged
this.draging = false;
this.isBeingDragged = false;
}
//Let's make some boxes!!
myBoxes[0] = new box(20,20,75,20,"#6AA121","First");
myBoxes[1] = new box(20,50,75,20,"#6AA121", "Second");
myBoxes[2] = new box(20,80,75,20,"#6AA121","third");
//here we draw everything
function draw() {
ctx.clearRect(0,0,c.width,c.height);
//Dropable area
ctx.fillStyle="red";
ctx.fillRect(c.width/2,0,c.width,c.height);
//Boxes!
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (b.draging) { //box on the move
//Also draw it on the original spot
ctx.fillStyle=b.rgb;
ctx.fillRect(b.xS,b.yS,b.w,b.h);
ctx.strokeRect(b.xS,b.yS,b.w,b.h);
ctx.font = "14px Arial";
ctx.strokeText(b.text, b.xS + 5 , b.yS + 15);
}
ctx.fillStyle=b.rgb;
ctx.fillRect(b.x,b.y,b.w,b.h);
ctx.strokeRect(b.x,b.y,b.w,b.h);
ctx.font = "14px Arial";
ctx.strokeText(b.text, b.x + 5 , b.y + 15);
}
for(var i = 0; i< myDroppedBoxes.length; i++) {
var b = myDroppedBoxes[i];
ctx.fillStyle=b.rgb;
ctx.fillRect(b.x,b.y,b.w,b.h);
ctx.strokeRect(b.x,b.y,b.w,b.h);
ctx.font = "14px Arial";
ctx.strokeText(b.text, b.x + 5 , b.y + 15);
}
}
function down(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (x>b.xS && x<b.xS+b.w && y>b.yS && y<b.yS+b.h) {
myBoxes[i].draging = true;
myBoxes[i].isBeingDragged = true;
}
}
for (var i = 0; i<myDroppedBoxes.length; i++) {
var b = myDroppedBoxes[i];
if (x>b.x && x<b.x + b.w && y>b.y && y<b.y + b.h) {
b.draging = true;
b.isBeingDragged = true;
}
}
draw();
}
function move(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (b.draging && b.isBeingDragged) {
myBoxes[i].x = x;
myBoxes[i].y = y;
if (b.x>c.width/2) {
var length = myDroppedBoxes.length ;
myDroppedBoxes[length] = new box(x,y,b.w,b.h,b.rgb,b.text);
myDroppedBoxes[length].draging = true;
myDroppedBoxes[length].isBeingDragged = true;
b.x = b.xS;
b.y = b.yS;
b.isBeingDragged = false;
}
}
}
for (var i = 0; i<myDroppedBoxes.length; i++) {
var b = myDroppedBoxes[i];
if (b.draging && b.isBeingDragged) {
b.x = x;
b.y = y;
}
}
draw();
}
function up(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i< myBoxes.length; i++) {
var b = myBoxes[i];
if (b.draging && b.isBeingDragged) {
//Let's see if the rectangle is inside the dropable area
if (b.x < c.width/2) {
myBoxes[i].x = b.xS;
myBoxes[i].y = b.yS;
myBoxes[i].draging = false;
b.isBeingDragged = false;
}
}
}
for (var i = 0; i< myDroppedBoxes.length; i++) {
var b = myDroppedBoxes[i];
if ( b.isBeingDragged) {
//Let's see if the rectangle is inside the dropable area
if (b.x>c.width/2) {
b.x = x;
b.y = y;
clubLegos(b);
plainTextMaker();
b.isBeingDragged = false;
}
else {
//No it's not, sending it back to its original spot
myDroppedBoxes.splice(i,1);
}
}
}
draw();
}
function clubLegos(b) {
// this loop is for checking that the box is lying near to which other box.
for(var j = 0; j < myDroppedBoxes.length; j++) {
var z = myDroppedBoxes[j];
if(!z.isBeingDragged) {
if(((x > z.x) && (x < (z.x + z.w))) && ((y > (z.y - 15)) && (y < (z.y + z.h + 10)))) {
b.x = z.x;
if( (y - z.y) >= 0) {
b.y = (z.y + z.h);
console.log("inside if " + y + " " + z.y);
}
else {
console.log("inside else " + y + " " + z.y);
b.y = (z.y - z.h);
}
}
}
}
}
function plainTextMaker() {
plainEnglishDiv = document.getElementById("plainEnglish");
plainEnglishDiv.innerHTML = "<h3>Here I am generating some plain text based on each drag and drop</h3>";
}
</script>
</html>
Here is the JS Fiddle for the same -
http://jsfiddle.net/akki166786/wa52f9pm/
Any help is greatly appreciated.
Thanks in advance.
Give each of your box objects something similar to Html's class.
This way you can drag every box with the same "class" simultaneously.
Add a club: property to each box.
// at top of app set a counter to make new clubs
var nextClub=0;
// each new box gets a unique "club"
function box(x,y,w,h,rgb,text) {
...
club:(nextClub++),
...
In clubLegos(b) when you're attaching dragged box b to another box z, also give the dropped box (b) & any member of (z)'s group a brand new club id:
// give the dropped box (b) & any member of (z)'s group a brand new club id
...if attaching b to z
b.club=nextClub;
for(var i=0;i<myBoxes.length;i++){
var bb=myBoxes[i];
if(bb.club==z.club){
bb.club=nextClub;
}
}
nextClub++;
This way, for example, if the mousedown has the user starting to drag a box with club==2, then you can add each myBoxes with club==2 into your dragging array and every box with club==2 will be dragged simultaneously.
I am using JSTreegraph plugin to draw a tree structure.
But now I need a drag, drop and attach feature wherein I can drag any node of the tree and attach to any other node and subsequently all the children of the first node will be now the grand-children of the new node (to which it is attached).
As far as I know this plugin doesnt seem to have this feature. It simply draws the structure based on the data object passed to it.
The plugin basically assigns a class Node to all the nodes(divs) of the tree and another class NodeHover to a node on hover. No id is assigned to these divs.
So I tried using JQuery Draggable just to see if any of the node can be moved by doing this
$('.Node').draggable();
$('.NodeHover').draggable();
But it doesnt seem to work. So can anyone help me with this.
How can I get drag and attach functionality?
*EDIT:: Pardon me am not so great with using Fiddle, so am sharing a sample code here for your use:*
HTML file: which will draw the sample tree
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link href="css/JSTreeGraph.css" rel="stylesheet" type="text/css" />
<script src="js/JSTreeGraph.js" type="text/javascript"></script>
<script src="js/jquery-1.7.1.min.js" type="text/javascript"></script>
<script src="js/jquery.ui.position.js" type="text/javascript"></script>
<style type="text/css">
.Container {
position: absolute;
top: 100px;
left: 50px;
id: Container;
}
</style>
</head>
<body>
<div id="tree">
Ctrl+Click to Add Node
<br />
Double Click to Expand or Collapse
<br />
Shift+Click to Delete Node
<br />
<select id="dlLayout" onchange="ChangeLayout()">
<option value="Horizontal">
Horizontal
</option>
<option value="Vertical" selected>
Vertical
</option>
</select>
<div class="Container" id="dvTreeContainer"></div>
<script type="text/javascript">
var selectedNode;
// Root node
var rootNode = { Content: "Onida", Nodes:[] };
// First Level
rootNode.Nodes[0] = { Content: "Employee Code", navigationType: "0"};
rootNode.Nodes[1] = { Content: "Problem Area", navigationType: "1" };
// Second Level
rootNode.Nodes[1].Nodes = [{ Content : "ACC-HO", Collapsed: true /* This node renders collapsed */ },
{ Content : "ACC-SALES" },
{ Content : "BUSI. HEAD", /*This node looks different*/ ToolTip: "Click ME!" },
{ Content : "CEO"},
{ Content : "HO-ADMIN"},
{ Content : "HO-FACTORY"},
{ Content : "SALES"}];
// Third Level
rootNode.Nodes[1].Nodes[0].Nodes = [{ Content: "Billing" },
{ Content: "Credit Limit" },
{ Content: "Reconciliation" }];
rootNode.Nodes[1].Nodes[1].Nodes = [{ Content: "Billing" },
{ Content: "Others" }];
rootNode.Nodes[1].Nodes[2].Nodes = [{ Content: "AC" },
{ Content: "CTV" },
{ Content: "DVD" },
{ Content: "Washing Machine" }];
rootNode.Nodes[1].Nodes[6].Nodes = [{ Content: "Appointments" },
{ Content: "Resignations" },
{ Content: "Others" }];
// Draw the tree for the first time
RefreshTree();
function RefreshTree() {
DrawTree({ Container: document.getElementById("dvTreeContainer"),
RootNode: rootNode,
Layout: document.getElementById("dlLayout").value,
OnNodeClickFirefox: NodeClickFF,
OnNodeClickIE: NodeClickIE,
OnNodeDoubleClick: NodeDoubleClick
});
}
//function
function NodeClickFF(e) {
if (e.shiftKey){
// Delete Node
if (!this.Node.Collapsed) {
for(var index=0; index<this.Node.ParentNode.Nodes.length; index++) {
if(this.Node.ParentNode.Nodes[index].Content == this.Node.Content) {
this.Node.ParentNode.Nodes.splice(index, 1);
break;
}
}
RefreshTree();
}
// return false;
}
else if (e.ctrlKey) {
// Add new Child if Expanded
if (!this.Node.Collapsed) {
if (!this.Node.Nodes) this.Node.Nodes = new Array();
var newNodeIndex = this.Node.Nodes.length;
this.Node.Nodes[newNodeIndex] = new Object();
this.Node.Nodes[newNodeIndex].Content = this.Node.Content + "." + (newNodeIndex + 1);
RefreshTree();
}
// return false;
}
else{
fnNodeProperties(this.Node);
}
}
function NodeClickIE() {
if (typeof(event) == "undefined" && event.ctrlKey) {
// Add new Child if Expanded
if (!this.Node.Collapsed) {
if (!this.Node.Nodes) this.Node.Nodes = new Array();
var newNodeIndex = this.Node.Nodes.length;
this.Node.Nodes[newNodeIndex] = new Object();
this.Node.Nodes[newNodeIndex].Content = this.Node.Content + "." + (newNodeIndex + 1);
RefreshTree();
}
}
else if (typeof(event) == "undefined" && event.shiftKey) {
// Delete Node
if (!this.Node.Collapsed) {
for(var index=0; index<this.Node.ParentNode.Nodes.length; index++) {
if(this.Node.ParentNode.Nodes[index].Content == this.Node.Content) {
this.Node.ParentNode.Nodes.splice(index, 1);
break;
}
}
RefreshTree();
}
}
else{
fnNodeProperties(this.Node);
}
}
function NodeDoubleClick() {
if (this.Node.Nodes && this.Node.Nodes.length > 0) { // If has children
this.Node.Collapsed = !this.Node.Collapsed;
RefreshTree();
}
}
function ChangeLayout() {
RefreshTree();
}
</script>
</div>
</body>
</html>
JSTreeGraph JS file: plugin js file
function DrawTree(options) {
// Prepare Nodes
PrepareNode(options.RootNode);
// Calculate Boxes Positions
if (options.Layout == "Vertical") {
PerformLayoutV(options.RootNode);
} else {
PerformLayoutH(options.RootNode);
}
// Draw Boxes
options.Container.innerHTML = "";
DrawNode(options.RootNode, options.Container, options);
// Draw Lines
DrawLines(options.RootNode, options.Container);
}
function DrawLines(node, container) {
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has children and Is Expanded
for (var j = 0; j < node.Nodes.length; j++) {
if(node.ChildrenConnectorPoint.Layout=="Vertical")
DrawLineV(container, node.ChildrenConnectorPoint, node.Nodes[j].ParentConnectorPoint);
else
DrawLineH(container, node.ChildrenConnectorPoint, node.Nodes[j].ParentConnectorPoint);
// Children
DrawLines(node.Nodes[j], container);
}
}
}
function DrawLineH(container, startPoint, endPoint) {
var midY = (startPoint.Y + ((endPoint.Y - startPoint.Y) / 2)); // Half path between start en end Y point
// Start segment
DrawLineSegment(container, startPoint.X, startPoint.Y, startPoint.X, midY, 1);
// Intermidiate segment
var imsStartX = startPoint.X < endPoint.X ? startPoint.X : endPoint.X; // The lower value will be the starting point
var imsEndX = startPoint.X > endPoint.X ? startPoint.X : endPoint.X; // The higher value will be the ending point
DrawLineSegment(container, imsStartX, midY, imsEndX, midY, 1);
// End segment
DrawLineSegment(container, endPoint.X, midY, endPoint.X, endPoint.Y, 1);
}
function DrawLineV(container, startPoint, endPoint) {
var midX = (startPoint.X + ((endPoint.X - startPoint.X) / 2)); // Half path between start en end X point
// Start segment
DrawLineSegment(container, startPoint.X, startPoint.Y, midX, startPoint.Y, 1);
// Intermidiate segment
var imsStartY = startPoint.Y < endPoint.Y ? startPoint.Y : endPoint.Y; // The lower value will be the starting point
var imsEndY = startPoint.Y > endPoint.Y ? startPoint.Y : endPoint.Y; // The higher value will be the ending point
DrawLineSegment(container, midX, imsStartY, midX, imsEndY, 1);
// End segment
DrawLineSegment(container, midX, endPoint.Y, endPoint.X, endPoint.Y, 1);
}
function DrawLineSegment(container, startX, startY, endX, endY, lineWidth) {
var lineDiv = document.createElement("div");
lineDiv.style.top = startY + "px";
lineDiv.style.left = startX + "px";
if (startX == endX) { // Vertical Line
lineDiv.style.width = lineWidth + "px";
lineDiv.style.height = (endY - startY) + "px";
}
else{ // Horizontal Line
lineDiv.style.width = (endX - startX) + "px";
lineDiv.style.height = lineWidth + "px";
}
lineDiv.className = "NodeLine";
container.appendChild(lineDiv);
}
function DrawNode(node, container, options) {
var nodeDiv = document.createElement("div");
nodeDiv.style.top = node.Top + "px";
nodeDiv.style.left = node.Left + "px";
nodeDiv.style.width = node.Width + "px";
nodeDiv.style.height = node.Height + "px";
if (node.Collapsed) {
nodeDiv.className = "NodeCollapsed";
} else {
nodeDiv.className = "Node";
}
if (node.Class)
nodeDiv.className = node.Class;
if (node.Content)
nodeDiv.innerHTML = "<div class='NodeContent'>" + node.Content + "</div>";
if (node.ToolTip)
nodeDiv.setAttribute("title", node.ToolTip);
nodeDiv.Node = node;
// Events
if (options.OnNodeClickIE){
//alert('OnNodeClick');
nodeDiv.onclick = options.OnNodeClickIE;
}
// Events
if (options.OnNodeClickFirefox){
//alert('OnNodeClick');
nodeDiv.onmousedown = options.OnNodeClickFirefox;
}
//on right click
if (options.OnContextMenu){
//alert('OnContextMenu');
nodeDiv.oncontextmenu = options.OnContextMenu;
}
if (options.OnNodeDoubleClick)
nodeDiv.ondblclick = options.OnNodeDoubleClick;
nodeDiv.onmouseover = function () { // In
this.PrevClassName = this.className;
this.className = "NodeHover";
};
nodeDiv.onmouseout = function () { // Out
if (this.PrevClassName) {
this.className = this.PrevClassName;
this.PrevClassName = null;
}
};
container.appendChild(nodeDiv);
// Draw children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has Children and is Expanded
for (var i = 0; i < node.Nodes.length; i++) {
DrawNode(node.Nodes[i], container, options);
}
}
}
function PerformLayoutV(node) {
var nodeHeight = 30;
var nodeWidth = 100;
var nodeMarginLeft = 40;
var nodeMarginTop = 20;
var nodeTop = 0; // defaultValue
// Before Layout this Node, Layout its children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) {
for (var i = 0; i < node.Nodes.length; i++) {
PerformLayoutV(node.Nodes[i]);
}
}
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // If Has Children and Is Expanded
// My Top is in the center of my children
var childrenHeight = (node.Nodes[node.Nodes.length - 1].Top + node.Nodes[node.Nodes.length - 1].Height) - node.Nodes[0].Top;
nodeTop = (node.Nodes[0].Top + (childrenHeight / 2)) - (nodeHeight / 2);
// Is my top over my previous sibling?
// Move it to the bottom
if (node.LeftNode && ((node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop) > nodeTop)) {
var newTop = node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop;
var diff = newTop - nodeTop;
/// Move also my children
MoveBottom(node.Nodes, diff);
nodeTop = newTop;
}
} else {
// My top is next to my top sibling
if (node.LeftNode)
nodeTop = node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop;
}
node.Top = nodeTop;
// The Left depends only on the level
node.Left = (nodeMarginLeft * (node.Level + 1)) + (nodeWidth * (node.Level + 1));
// Size is constant
node.Height = nodeHeight;
node.Width = nodeWidth;
// Calculate Connector Points
// Child: Where the lines get out from to connect this node with its children
var pointX = node.Left + nodeWidth;
var pointY = nodeTop + (nodeHeight/2);
node.ChildrenConnectorPoint = { X: pointX, Y: pointY, Layout: "Vertical" };
// Parent: Where the line that connect this node with its parent end
pointX = node.Left;
pointY = nodeTop + (nodeHeight/2);
node.ParentConnectorPoint = { X: pointX, Y: pointY, Layout: "Vertical" };
}
function PerformLayoutH(node) {
var nodeHeight = 30;
var nodeWidth = 100;
var nodeMarginLeft = 20;
var nodeMarginTop = 50;
var nodeLeft = 0; // defaultValue
// Before Layout this Node, Layout its children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length>0) {
for (var i = 0; i < node.Nodes.length; i++) {
PerformLayoutH(node.Nodes[i]);
}
}
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // If Has Children and Is Expanded
// My left is in the center of my children
var childrenWidth = (node.Nodes[node.Nodes.length-1].Left + node.Nodes[node.Nodes.length-1].Width) - node.Nodes[0].Left;
nodeLeft = (node.Nodes[0].Left + (childrenWidth / 2)) - (nodeWidth / 2);
// Is my left over my left node?
// Move it to the right
if(node.LeftNode&&((node.LeftNode.Left+node.LeftNode.Width+nodeMarginLeft)>nodeLeft)) {
var newLeft = node.LeftNode.Left + node.LeftNode.Width + nodeMarginLeft;
var diff = newLeft - nodeLeft;
/// Move also my children
MoveRigth(node.Nodes, diff);
nodeLeft = newLeft;
}
} else {
// My left is next to my left sibling
if (node.LeftNode)
nodeLeft = node.LeftNode.Left + node.LeftNode.Width + nodeMarginLeft;
}
node.Left = nodeLeft;
// The top depends only on the level
node.Top = (nodeMarginTop * (node.Level + 1)) + (nodeHeight * (node.Level + 1));
// Size is constant
node.Height = nodeHeight;
node.Width = nodeWidth;
// Calculate Connector Points
// Child: Where the lines get out from to connect this node with its children
var pointX = nodeLeft + (nodeWidth / 2);
var pointY = node.Top + nodeHeight;
node.ChildrenConnectorPoint = { X: pointX, Y: pointY, Layout:"Horizontal" };
// Parent: Where the line that connect this node with its parent end
pointX = nodeLeft + (nodeWidth / 2);
pointY = node.Top;
node.ParentConnectorPoint = { X: pointX, Y: pointY, Layout: "Horizontal" };
}
function MoveRigth(nodes, distance) {
for (var i = 0; i < nodes.length; i++) {
nodes[i].Left += distance;
if (nodes[i].ParentConnectorPoint) nodes[i].ParentConnectorPoint.X += distance;
if (nodes[i].ChildrenConnectorPoint) nodes[i].ChildrenConnectorPoint.X += distance;
if (nodes[i].Nodes) {
MoveRigth(nodes[i].Nodes, distance);
}
}
}
function MoveBottom(nodes, distance) {
for (var i = 0; i < nodes.length; i++) {
nodes[i].Top += distance;
if (nodes[i].ParentConnectorPoint) nodes[i].ParentConnectorPoint.Y += distance;
if (nodes[i].ChildrenConnectorPoint) nodes[i].ChildrenConnectorPoint.Y += distance;
if (nodes[i].Nodes) {
MoveBottom(nodes[i].Nodes, distance);
}
}
}
function PrepareNode(node, level, parentNode, leftNode, rightLimits) {
if (level == undefined) level = 0;
if (parentNode == undefined) parentNode = null;
if (leftNode == undefined) leftNode = null;
if (rightLimits == undefined) rightLimits = new Array();
node.Level = level;
node.ParentNode = parentNode;
node.LeftNode = leftNode;
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has children and is expanded
for (var i = 0; i < node.Nodes.length; i++) {
var left = null;
if (i == 0 && rightLimits[level]!=undefined) left = rightLimits[level];
if (i > 0) left = node.Nodes[i - 1];
if (i == (node.Nodes.length-1)) rightLimits[level] = node.Nodes[i];
PrepareNode(node.Nodes[i], level + 1, node, left, rightLimits);
}
}
}
JSTreeGraph CSS file:
.NodeContent
{
font-family:Verdana;
font-size:small;
}
.Node
{
position:absolute;
background-color: #CCDAFF;
border: 1px solid #5280FF;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}
.NodeHover
{
position:absolute;
background-color: #8FADFF;
border: 1px solid #5280FF;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}
.NodeCollapsed
{
position:absolute;
background-color: #8FADFF;
border: 2px solid black;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}
.NodeLine
{
background-color: #000066;
position:absolute;
overflow:hidden;
}
I supose that what you need is to stablish a mechanism to modify your tree logic structure on dragging and then reload the whole tree element. This way the plugin will render your new modified structure.
This is not an easy problem and you have not outlined the exact specifications.
When you move a node, should all child nodes move with it?
What happens to a node and its child elements when a node is dropped/attached?
The plugin that you are using works well for drawing static trees, but it is not written in such a way to allow for dynamically editing the tree.
I have to agree with #Bardo in that the easiest way to make this work for your needs is to understand how the tree has been manipulated. (Thankfully the plugin seems to provide an onNodeClick option which will allow you to understand which node you are intending to manipulate). Once the tree has been manipulated then it must be completely redrawn. (There doesn't appear to be a good way to partially draw a tree).
I have a UIWebView with an HTML page completely loaded. The UIWebView has a frame of 320 x 480 and scrolls horizontally. I can get the current offset a user is currently at. I would like to find the closest anchor using the XY offset so I can "jump to" that anchors position. Is this at all possible? Can someone point me to a resource in Javascript for doing this?
<a id="p-1">Text Text Text Text Text Text Text Text Text<a id="p-2">Text Text Text Text Text Text Text Text Text ...
Update
My super sad JS code:
function posForElement(e)
{
var totalOffsetY = 0;
do
{
totalOffsetY += e.offsetTop;
} while(e = e.offsetParent)
return totalOffsetY;
}
function getClosestAnchor(locationX, locationY)
{
var a = document.getElementsByTagName('a');
var currentAnchor;
for (var idx = 0; idx < a.length; ++idx)
{
if(a[idx].getAttribute('id') && a[idx+1])
{
if(posForElement(a[idx]) <= locationX && locationX <= posForElement(a[idx+1]))
{
currentAnchor = a[idx];
break;
}
else
{
currentAnchor = a[0];
}
}
}
return currentAnchor.getAttribute('id');
}
Objective-C
float pageOffset = 320.0f;
NSString *path = [[NSBundle mainBundle] pathForResource:#"GetAnchorPos" ofType:#"js"];
NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
[webView stringByEvaluatingJavaScriptFromString:jsCode];
NSString *execute = [NSString stringWithFormat:#"getClosestAnchor('%f', '0')", pageOffset];
NSString *anchorID = [webView stringByEvaluatingJavaScriptFromString:execute];
[UPDATE] I rewrote the code to match all the anchors that have an id, and simplified the comparison of the norm of the vectors in my sortByDistance function.
Check my attempt on jsFiddle (the previous one was here ).
The javascript part :
// findPos : courtesy of #ppk - see http://www.quirksmode.org/js/findpos.html
var findPos = function(obj) {
var curleft = 0,
curtop = 0;
if (obj.offsetParent) {
curleft = obj.offsetLeft;
curtop = obj.offsetTop;
while ((obj = obj.offsetParent)) {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
}
}
return [curleft, curtop];
};
var findClosestAnchor = function (anchors) {
var sortByDistance = function(element1, element2) {
var pos1 = findPos( element1 ),
pos2 = findPos( element2 );
// vect1 & vect2 represent 2d vectors going from the top left extremity of each element to the point positionned at the scrolled offset of the window
var vect1 = [
window.scrollX - pos1[0],
window.scrollY - pos1[1]
],
vect2 = [
window.scrollX - pos2[0],
window.scrollY - pos2[1]
];
// we compare the length of the vectors using only the sum of their components squared
// no need to find the magnitude of each (this was inspired by Mageek’s answer)
var sqDist1 = vect1[0] * vect1[0] + vect1[1] * vect1[1],
sqDist2 = vect2[0] * vect2[0] + vect2[1] * vect2[1];
if ( sqDist1 < sqDist2 ) return -1;
else if ( sqDist1 > sqDist2 ) return 1;
else return 0;
};
// Convert the nodelist to an array, then returns the first item of the elements sorted by distance
return Array.prototype.slice.call( anchors ).sort( sortByDistance )[0];
};
You can retrieve and cache the anchors like so when the dom is ready : var anchors = document.body.querySelectorAll('a[id]');
I’ve not tested it on a smartphone yet but I don’t see any reasons why it wouldn’t work.
Here is why I used the var foo = function() {}; form (more javascript patterns).
The return Array.prototype.slice.call( anchors ).sort( sortByDistance )[0]; line is actually a bit tricky.
document.body.querySelectorAll('a['id']') returns me a NodeList with all the anchors that have the attribute "id" in the body of the current page.
Sadly, a NodeList object does not have a "sort" method, and it is not possible to use the sort method of the Array prototype, as it is with some other methods, such as filter or map (NodeList.prototype.sort = Array.prototype.sort would have been really nice).
This article explains better that I could why I used Array.prototype.slice.call to turn my NodeList into an array.
And finally, I used the Array.prototype.sort method (along with a custom sortByDistance function) to compare each element of the NodeList with each other, and I only return the first item, which is the closest one.
To find the position of the elements that use fixed positionning, it is possible to use this updated version of findPos : http://www.greywyvern.com/?post=331.
My answer may not be the more efficient (drdigit’s must be more than mine) but I preferred simplicity over efficiency, and I think it’s the easiest one to maintain.
[YET ANOTHER UPDATE]
Here is a heavily modified version of findPos that works with webkit css columns (with no gaps):
// Also adapted from PPK - this guy is everywhere ! - check http://www.quirksmode.org/dom/getstyles.html
var getStyle = function(el,styleProp)
{
if (el.currentStyle)
var y = el.currentStyle[styleProp];
else if (window.getComputedStyle)
var y = document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp);
return y;
}
// findPos : original by #ppk - see http://www.quirksmode.org/js/findpos.html
// made recursive and transformed to returns the corect position when css columns are used
var findPos = function( obj, childCoords ) {
if ( typeof childCoords == 'undefined' ) {
childCoords = [0, 0];
}
var parentColumnWidth,
parentHeight;
var curleft, curtop;
if( obj.offsetParent && ( parentColumnWidth = parseInt( getStyle( obj.offsetParent, '-webkit-column-width' ) ) ) ) {
parentHeight = parseInt( getStyle( obj.offsetParent, 'height' ) );
curtop = obj.offsetTop;
column = Math.ceil( curtop / parentHeight );
curleft = ( ( column - 1 ) * parentColumnWidth ) + ( obj.offsetLeft % parentColumnWidth );
curtop %= parentHeight;
}
else {
curleft = obj.offsetLeft;
curtop = obj.offsetTop;
}
curleft += childCoords[0];
curtop += childCoords[1];
if( obj.offsetParent ) {
var coords = findPos( obj.offsetParent, [curleft, curtop] );
curleft = coords[0];
curtop = coords[1];
}
return [curleft, curtop];
}
I found a way to make it, without using scrollOffset. It's a bit complicated so if you have any question to understand it just comment.
HTML :
<body>
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
<a />Text Text Text Text Text Text Text Text Text
<br /><br /><br /><br /><br />
<a />Text Text Text Text Text Text Text Text Text
</body>
CSS :
body
{
height:3000px;
}
JS :
var tempY;
function getClosestAnchor(e)
{
if((window.event?event.keyCode:e.which)!=97)return;
var allAnchors=document.getElementsByTagName("a");
var allDiff=[];
for(var a=0;a<allAnchors.length;a++)allDiff[a]=margeY(allAnchors[a])-tempY;
var smallest=allDiff[0];
for(var a=1;a<allDiff.length;a++)
{
if(Math.abs(smallest)>Math.abs(allDiff[a]))
{
smallest=allDiff[a];
}
}
window.scrollBy(0,smallest);
}
function margeY(obj)
{
var posY=0;
if(!obj.offsetParent)return;
do posY+=obj.offsetTop;
while(obj=obj.offsetParent);
return posY;
}
function update(e)
{
if(e.pageY)tempY=e.pageY;
else tempY=e.clientY+(document.documentElement.scrollTop||document.body.scrollTop)-document.documentElement.clientTop;
}
window.onkeypress=getClosestAnchor;
window.onmousemove=update;
Here is a fiddle demonstration : http://jsfiddle.net/jswuC/
Bonus : You don't have to specify an id to all the anchors.
Phew! I finished!
JS :
var x=0,y=0;//Here are the given X and Y, you can change them
var idClosest;//Id of the nearest anchor
var smallestIndex;
var couplesXY=[];
var allAnchors;
var html=document.getElementsByTagName("html")[0];
html.style.width="3000px";//You can change 3000, it's to make the possibility of horizontal scroll
html.style.height="3000px";//Here too
function random(min,max)
{
var nb=min+(max+1-min)*Math.random();
return Math.floor(nb);
}
function left(obj)//A remixed function of this site http://www.quirksmode.org/js/findpos.html
{
if(obj.style.position=="absolute")return parseInt(obj.style.left);
var posX=0;
if(!obj.offsetParent)return;
do posX+=obj.offsetLeft;
while(obj=obj.offsetParent);
return posX;
}
function top(obj)
{
if(obj.style.position=="absolute")return parseInt(obj.style.top);
var posY=0;
if(!obj.offsetParent)return;
do posY+=obj.offsetTop;
while(obj=obj.offsetParent);
return posY;
}
function generateRandomAnchors()//Just for the exemple, you can delete the function if you have already anchors
{
for(var a=0;a<50;a++)//You can change 50
{
var anchor=document.createElement("a");
anchor.style.position="absolute";
anchor.style.width=random(0,100)+"px";//You can change 100
anchor.style.height=random(0,100)+"px";//You can change 100
anchor.style.left=random(0,3000-parseInt(anchor.style.width))+"px";//If you changed 3000 from
anchor.style.top=random(0,3000-parseInt(anchor.style.height))+"px";//the top, change it here
anchor.style.backgroundColor="black";
anchor.id="Anchor"+a;
document.body.appendChild(anchor);
}
}
function getAllAnchors()
{
allAnchors=document.getElementsByTagName("a");
for(var a=0;a<allAnchors.length;a++)
{
couplesXY[a]=[];
couplesXY[a][0]=left(allAnchors[a]);
couplesXY[a][1]=top(allAnchors[a]);
}
}
function findClosestAnchor()
{
var distances=[];
for(var a=0;a<couplesXY.length;a++)distances.push(Math.pow((x-couplesXY[a][0]),2)+Math.pow((y-couplesXY[a][1]),2));//Math formula to get the distance from A to B (http://euler.ac-versailles.fr/baseeuler/lexique/notion.jsp?id=122). I removed the square root not to slow down the calculations
var smallest=distances[0];
smallestIndex=0;
for(var a=1;a<distances.length;a++)if(smallest>distances[a])
{
smallest=distances[a];
smallestIndex=a;
}
idClosest=allAnchors[smallestIndex].id;
alert(idClosest);
}
function jumpToIt()
{
window.scrollTo(couplesXY[smallestIndex][0],couplesXY[smallestIndex][1]);
allAnchors[smallestIndex].style.backgroundColor="red";//Color it to see it
}
generateRandomAnchors();
getAllAnchors();
findClosestAnchor();
jumpToIt();
Fiddle : http://jsfiddle.net/W8LBs/2
PS : If you open this fiddle on a smartphone, it doesn't work (I don't know why) but if you copy this code in a sample on a smartphone, it works (but you must specify the <html> and the <body> section).
This answer has not received enough attention.
Complete sample, fast (binary search) with caching for positions.
Fixed height and width, id of the closest anchor and scrollto
<!DOCTYPE html>
<html lang="en">
<head>
<meta>
<title>Offset 2</title>
<style>
body { font-family:helvetica,arial; font-size:12px; }
</style>
<script>
var ui = reqX = reqY = null, etop = eleft = 0, ref, cache;
function createAnchors()
{
if (!ui)
{
ui = document.getElementById('UIWebView');
reqX = document.getElementById('reqX');
reqY = document.getElementById('reqY');
var h=[], i=0;
while (i < 1000)
h.push('<a>fake anchor ... ',i,'</a> <a href=#>text for anchor <b>',(i++),'</b></a> ');
ui.innerHTML = '<div style="padding:10px;width:700px">' + h.join('') + '</div>';
cache = [];
ref = Array.prototype.slice.call(ui.getElementsByTagName('a'));
i = ref.length;
while (--i >= 0)
if (ref[i].href.length == 0)
ref.splice(i,1);
}
}
function pos(i)
{
if (!cache[i])
{
etop = eleft = 0;
var e=ref[i];
if (e.offsetParent)
{
do
{
etop += e.offsetTop;
eleft += e.offsetLeft;
} while ((e = e.offsetParent) && e != ui)
}
cache[i] = [etop, eleft];
}
else
{
etop = cache[i][0];
eleft = cache[i][1];
}
}
function find()
{
createAnchors();
if (!/^\d+$/.test(reqX.value))
{
alert ('I need a number for X');
return;
}
if (!/^\d+$/.test(reqY.value))
{
alert ('I need a number for Y');
return;
}
var
x = reqX.value,
y = reqY.value,
low = 0,
hi = ref.length + 1,
med,
limit = (ui.scrollHeight > ui.offsetHeight) ? ui.scrollHeight - ui.offsetHeight : ui.offsetHeight - ui.scrollHeight;
if (y > limit)
y = limit;
if (x > ui.scrollWidth)
x = (ui.scrollWidth > ui.offsetWidth) ? ui.scrollWidth : ui.offsetWidth;
while (low < hi)
{
med = (low + ((hi - low) >> 1));
pos(med);
if (etop == y)
{
low = med;
break;
}
if (etop < y)
low = med + 1;
else
hi = med - 1;
}
var ctop = etop;
if (eleft != x)
{
if (eleft > x)
while (low > 0)
{
pos(--low);
if (etop < ctop || eleft < x)
{
pos(++low);
break;
}
}
else
{
hi = ref.length;
while (low < hi)
{
pos(++low);
if (etop > ctop || eleft > x)
{
pos(--low);
break;
}
}
}
}
ui.scrollTop = etop - ui.offsetTop;
ui.scrollLeft = eleft - ui.offsetLeft;
ref[low].style.backgroundColor = '#ff0';
alert(
'Requested position: ' + x + ', ' + y +
'\nScrollTo position: ' + ui.scrollLeft + ', '+ ui.scrollTop +
'\nClosest anchor id: ' + low
);
}
</script>
</head>
<body>
<div id=UIWebView style="width:320px;height:480px;overflow:auto;border:solid 1px #000"></div>
<label for="req">X: <input id=reqX type=text size=5 maxlength=5 value=200></label>
<label for="req">Y: <input id=reqY type=text size=5 maxlength=5 value=300></label>
<input type=button value="Find closest anchor" onclick="find()">
</body>
</html>