Login Register

dnd.constrainedMoveable question - is it possible to get the moveable item to 'follow' the mouse pointer?

Hi,

I'm hoping someone can help. I have been using constrainedMoveable and have a page with a container div and a smaller div inside. At the moment, the user is able to drag the smaller div around inside the constraints of the parent container and this works very well.

However, I'd been trying to get the smaller div to track with the pointer as soon as the user moves the mouse into the containing div - in other words: as soon as they move into the containing div, I'd like the smaller div to follow the pointer around.

I can see how this might be achieved just with javascript, but I'm sure there's a good way to do this whilst using dojo.dnd.move.constrainedMoveable, so I was hoping maybe someone on this forum would be kind enough to assist?

Attached is the javascript for the constrained moveable item and in the html body, I've got two divs: the container div is called image-area and the smaller (moveable) div is called viewport (and is inside the container div):

dojo.require("dojo.dnd.Moveable");
dojo.require("dojo.dnd.move");

dojo.addOnLoad(function() {
    var constraintContainer = function() {
        var marginBox = dojo.marginBox("image-area");
        boundary = {};
        // Top, Left, Width, Height
        boundary["t"] = 0;
        boundary["l"] = 0;
        boundary["w"] = marginBox.l + marginBox.w;
        boundary["h"] = marginBox.h + marginBox.t;
        return boundary;
    }
    var moveIt = new dojo.dnd.move.constrainedMoveable("viewport", {
        constraints: constraintContainer,
        within: true
    });
});

Can anyone offer any advice regarding this? Thanks very much in advance.

Simple

Actually it is relatively simple to do — just simulate start/end of the drag. In my snippet I assume you use the trunk.

var parent = dojo.byId("image-area");
var moveIt = ...; // propagated from your snippet
dojo.connect(parent, "onmouseenter", function(e){
    // probably you want to massage e.pageX, and e.pageY
    // to suit your needs
    moveIt._mover = moveIt.mover(moveIt.node, e, moveIt);
});
dojo.connect(parent, "onmouseleave", function(){
    moveIt._mover.destroy();
    delete moveIt._mover;
});

Keep in mind that this snippet is untested but it should give you an idea.

Just out of curiosity: why didn't you use dojo.dnd.move.parentConstrainedMoveable?

Hi Eugene, Thanks very much

Hi Eugene,

Thanks very much for getting back to me and for the great suggestion regarding move.parentConstrainedMoveable - that cut down my code significantly ;)

Now I have this (as per your suggestions):

dojo.require("dojo.dnd.Moveable");
dojo.require("dojo.dnd.move");

dojo.addOnLoad(function() {
    var parent = dojo.byId("image-area");
    var moveIt = new dojo.dnd.move.parentConstrainedMoveable("viewport", {
        within: true
    });
    dojo.connect(parent, "onmouseenter",
    function(e) {
        moveIt._mover = moveIt.mover(moveIt.node, e, moveIt);
    });
    dojo.connect(parent, "onmouseleave",
    function() {
        moveIt._mover.destroy();
        delete moveIt._mover;
    });
});

At first, I tried that example with the current stable (1.1.1) and got an 'operation not supported' error, so I tested it with the current svn trunk and - although I don't get that error any more - I do now seem to get these errors in the firebug console:

node is undefined
dojoroot/dojo/./_base/html.js
Line 639

(onmouseenter)

and

moveIt._mover is undefined
dojoroot/dojo/./_base/html.js
Line 639

(onmouseleave)

Would you have any suggestions as to what I could do here? Anything you can think of would be most gratefully appreciated!

Thanks very much once again for your time.
Best,
A

One suggestion is to check

One suggestion is to check in "onmouseleave" for moveIt._mover and do a destroy sequence conditionally — if it is not defined, don't do it.

If it fails, you can make a minimalistic example and send the HTML file to me at my_name @ this domain, and I'll look at it in the debugger.

Thanks again for your help,

Thanks again for your help, Eugene - I did what you suggested and it looks like moveIt._mover never gets defined. I've sent an example email to you, hope to hear from you soon.

Best,
A

My mistake...

My bad — I missed the necessary "new" keyword while constructing the mover. Updated snippet:

var parent = dojo.byId("image-area");
var moveIt = ...; // propagated from your snippet
dojo.connect(parent, "onmouseenter", function(e){
    // probably you want to massage e.pageX, and e.pageY
    // to suit your needs
    moveIt._mover = new moveIt.mover(moveIt.node, e, moveIt);
    dojo.stopEvent(e);
});
dojo.connect(parent, "onmouseleave", function(e){
    if(moveIt._mover){
        moveIt._mover.destroy();
        delete moveIt._mover;
    }
    dojo.stopEvent(e);
});

I added stopping event for good measure, and put the check for _mover in. Now it is up to you to massage e.pageX and e.pageY parameters so your moveable jumps to the cursor, when it enters — I don't know what criteria you want to use for that. You may end up creating an artificial event for that — all you need is to pass {pageX: newX, pageY: newY, button: e.button} or something like that. Only these 3 attributes are used by the mover's code.

Eugene - that's absolutely

Eugene - that's absolutely brilliant! thanks very much for your help with this. I've attached all the javascript code here for reference in the hope that someone else might find it useful:

dojo.require("dojo.dnd.Moveable");
			dojo.require("dojo.dnd.move");
			
			dojo.addOnLoad(function() {
			    var parent = dojo.byId("image-area");
			    var moveIt = new dojo.dnd.move.parentConstrainedMoveable("viewport", {
			        within: true
			    });
				
			    dojo.connect(parent, "onmouseenter",
			    function(e) {
					var viewport = dojo.byId("viewport");
					dojo.style("viewport", {top: (e.clientY - viewport.clientHeight/2)+'px', left: (e.clientX - viewport.clientWidth/2)+'px'});
			        moveIt._mover = new moveIt.mover(moveIt.node, e, moveIt);
			    });
				
			    dojo.connect(parent, "onmouseleave",
				function(e) {
					if (moveIt._mover) {
					//	console.log('destroying..')
						moveIt._mover.destroy();
						delete moveIt._mover;
					}
					dojo.stopEvent(e);
			    });
			});

Thanks again!

Best,
A

Hi, Hope you don't mind me

Hi,

Hope you don't mind me asking a follow-on question:

Is there a decent way using dojo.connect to monitor every time the internal div moves and call a function? I've tried using "onmousemove" for this but it seems to slow things down somewhat.

What I'm essentially trying to do is call a function that reacts to the relative x,y position of the internal div. Is there a good way to manage this in dojo (or is 'onmousemove' potentially the most effective way to do it?)

Thanks in advance,
A

Use local events

Use local events on Moveable (see Moveable.js for details). For example:

dojo.connect(moveIt, "onMoved", function(mover, leftTop){
  console.log("Ma, we've got moved to (" + leftTop.l + ", " + leftTop.t + ")!");
});

Granted, hooking up to onMove, onMoving, onMoved will be taxing — they are called on every mouse move. It is not recommended to do so. Instead consider hooking up to onMoveStop that is called when user is done moving the node. Or use "delayed" notifications:

var timer = null, lt;
var delayedHandler = function(){
  console.log("Ma, we've got moved to (" + lt.l + ", " + lt.t + ")!");
  timer = null;
};
dojo.connect(moveIt, "onMoved", function(mover, leftTop){
  lt = leftTop;
  if(!timer){
    timer = setTimeout(delayedHandler, 25 /* ms */);
  }
});

Or something along those lines. Use your imagination. The snippet above is a rough approximation of TimedMoveable (see code for details) — you can attach your code to onMoved and enjoy throttling for free.

That's great - thanks very

That's great - thanks very much once again for the code snippets - they're both very useful and I'm looking forward to doing some testing with them.

In the meantime, I came up with this - as you said, it would be very taxing, so I'll follow your suggestions and rewrite it to use (maybe) something like the delayHandler fairly soon. This responds to mouse movements and passes the mover node offsets out to another function:

dojo.connect(parent, "onmousemove",
function(e) {
    if (moveIt._mover) {
        updateIt(moveIt._mover.node.offsetLeft, moveIt._mover.node.offsetTop);
    }
});

Thanks again for your time, help and the excellent suggestions - they've been a great help.

Best,
A