I developped a code including a table with all its cells as droppable. The table container is div with a fix height and a scrollbar.
I would like drag an element (yellow square in my example) into the last table cell at the bottom of my table. Everything works fine, but to activate the scrollbar of my div container when I am dragging the element, I must move the mouse all the time.
Is there a possibility to scroll down automatically when my element is dragging near the bottom of my div container without moving mouse?
Here is my example : http://jsbin.com/upunek/19/edit
Thanks in advance
I figure it out this morning.
I created setInterval function when the drag event position is located at 70px of the border
Here is the fiddle I made : http://jsfiddle.net/pPn3v/22/
var yellow = $('#yellow');
var offset = yellow.offset();
var offsetWidth = offset.left + yellow.width();
var offsetHeight = offset.top + yellow.height();
var red = $('#red');
var intRightHandler = null;
var intLeftHandler = null;
var intTopHandler= null;
var intBottomHandler= null;
var distance = 70;
var timer = 100;
var step = 10;
function clearInetervals()
{
clearInterval(intRightHandler);
clearInterval(intLeftHandler);
clearInterval(intTopHandler);
clearInterval(intBottomHandler);
}
red.draggable({
start : function(){},
stop: function(){clearInetervals(); },
drag : function(e){
var isMoving = false;
//Left
if((e.pageX - offset.left) <= distance)
{
isMoving = true;
clearInetervals();
intLeftHandler= setInterval(function(){
yellow.scrollLeft(yellow.scrollLeft() - step)
},timer);
console.log('left')
}
//Right
if(e.pageX >= (offsetWidth - distance))
{
isMoving = true;
clearInetervals();
intRightHandler = setInterval(function(){
yellow.scrollLeft(yellow.scrollLeft() + step)
},timer);
console.log('right')
}
//Top
if((e.pageY - offset.top) <= distance)
{
isMoving = true;
clearInetervals();
intTopHandler= setInterval(function(){
yellow.scrollTop(yellow.scrollTop() - step)
},timer);
console.log('top')
}
//Bottom
if(e.pageY >= (offsetHeight - distance))
{
isMoving = true;
clearInetervals();
intBottomHandler= setInterval(function(){
yellow.scrollTop(yellow.scrollTop() + step)
},timer);
console.log('bottom')
}
//No events
if(!isMoving)
clearInetervals();
}
});
Related
I recently began to learn Konva-JS... please help me :)
<script>
var width = window.innerWidth;
var height = window.innerHeight;
function loadImages(sources, callback) {
var assetDir = '/assets/';
var images = {};
var loadedImages = 0;
var numImages = 0;
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = assetDir + sources[src];
}
}
function isNearOutline(animal, outline) {
var a = animal;
var o = outline;
var ax = a.getX();
var ay = a.getY();
if(ax > o.x - 20 && ax < o.x + 20 && ay > o.y - 20 && ay < o.y + 20) {
return true;
}
else {
return false;
}
}
function drawBackground(background, beachImg, text) {
var context = background.getContext();
context.drawImage(beachImg, 0, 0);
context.setAttr('font', '20pt Calibri');
context.setAttr('textAlign', 'center');
context.setAttr('fillStyle', 'white');
context.fillText(text, background.getStage().getWidth() / 2, 40);
}
function initStage(images) {
var stage = new Konva.Stage({
container: 'container',
width: 578,
height: 530
});
var background = new Konva.Layer();
var animalLayer = new Konva.Layer();
var animalShapes = [];
var score = 0;
// image positions
var animals = {
snake: {
x: 10,
y: 70
},
giraffe: {
x: 90,
y: 70
},
monkey: {
x: 275,
y: 70
},
lion: {
x: 400,
y: 70
}
};
var outlines = {
snake_black: {
x: 275,
y: 350
},
giraffe_black: {
x: 390,
y: 250
},
monkey_black: {
x: 300,
y: 420
},
lion_black: {
x: 100,
y: 390
}
};
// create draggable animals
for(var key in animals) {
// anonymous function to induce scope
(function() {
var privKey = key;
var anim = animals[key];
var animal = new Konva.Image({
image: images[key],
x: anim.x,
y: anim.y,
draggable: true
});
animal.on('dragstart', function() {
this.moveToTop();
animalLayer.draw();
});
/*
* check if animal is in the right spot and
* snap into place if it is
*/
animal.on('dragend', function() {
var outline = outlines[privKey + '_black'];
if(!animal.inRightPlace && isNearOutline(animal, outline)) {
animal.position({
x : outline.x,
y : outline.y
});
animalLayer.draw();
animal.inRightPlace = true;
if(++score >= 4) {
var text = 'You win! Enjoy your booty!';
drawBackground(background, images.beach, text);
}
// disable drag and drop
setTimeout(function() {
animal.draggable(false);
}, 50);
}
});
// make animal glow on mouseover
animal.on('mouseover', function() {
animal.image(images[privKey + '_glow']);
animalLayer.draw();
document.body.style.cursor = 'pointer';
});
// return animal on mouseout
animal.on('mouseout', function() {
animal.image(images[privKey]);
animalLayer.draw();
document.body.style.cursor = 'default';
});
animal.on('dragmove', function() {
document.body.style.cursor = 'pointer';
});
animalLayer.add(animal);
animalShapes.push(animal);
})();
}
// create animal outlines
for(var key in outlines) {
// anonymous function to induce scope
(function() {
var imageObj = images[key];
var out = outlines[key];
var outline = new Konva.Image({
image: imageObj,
x: out.x,
y: out.y
});
animalLayer.add(outline);
})();
}
stage.add(background);
stage.add(animalLayer);
drawBackground(background, images.beach, 'Ahoy! Put the animals on the beach!');
}
var sources = {
beach: 'beach.png',
snake: 'snake.png',
snake_glow: 'snake-glow.png',
snake_black: 'snake-black.png',
lion: 'lion.png',
lion_glow: 'lion-glow.png',
lion_black: 'lion-black.png',
monkey: 'monkey.png',
monkey_glow: 'monkey-glow.png',
monkey_black: 'monkey-black.png',
giraffe: 'giraffe.png',
giraffe_glow: 'giraffe-glow.png',
giraffe_black: 'giraffe-black.png'
};
loadImages(sources, initStage);
</script>
as we can see in this example Animals_on_the_Beach_Game the animal's images are drag-able and can be drop ever where.... but I want to change it in the way that it just can drop on the specific place ... what can I do ?
thank you :)
This is more of a design question, as letting go of the mouse button isn't something you can prevent. It would also be non-intuitive to keep the image attached to the mouse position as you would then need a new mouse event to associate with dropping it. What I've done for a drag and drop UI was to either (1) destroy the dropped shape, or if that wasn't an option, (2) animate the shape back (i.e. snap back) to its original position. Alternatively, you might (3) find the closest likely valid drop target and snap to that location.
First you define lionOrigin, that maybe you already have.
You have to implement the call on the dragend event of the object dragged, so let's say the lion. You have to check position of the lion in relation to the end desired position, let's call it lionDestiny. That can be done with a simple grometry: calculate the distance between to point. We do that with distanceA2B() function.
Now you can establish an offset inside wich you can snap the object, as it is close enough. If the minimal offset is not achieved, then you place the lion back on lionOrigin.
Al last, in konvajs you can use .x() and .y() to easily get or set position to lion.
Something like this:
var lionOrigin = [50,50];
var lionDestiny = [200,200];
var offset = 20;
distanceA2B(a,b) {
return Math.sqrt( ((a[0]-b[0])*(a[0]-b[0])) + ((a[1]-b[1])*(a[1]-b[1])) );
}
lion.on('dragend', (e) => {
var d = distanceA2B([lion.x(),lion.y()],lionDestiny);
if(d<offset){
lion.x(lionDestiny[0]);
lion.y(lionDestiny[1]);
}else{
lion.x(lionOrigin[0]);
lion.y(lionOrigin[1]);
}
});
Hope this helps!
It would have been better if you could explain your question more when you say you want to move any shape to a specific position. Though konva.js provides you with various events through which you can do this. For example, suppose you want to interchange the location of two shapes when you drag and move the first shape to the second and drop it there. In this case, you can use dragend event of konva. So when you move the target element to another element and drop it there, check if they are intersecting each other or not and then interchange their coordinates with each other.
Here is the function to find the intersection between two elements:
haveIntersection(r1, r2) {
return !(
r2.x > r1.x + r1.width ||
r2.x + r2.width < r1.x ||
r2.y > r1.y + r1.height ||
r2.y + r2.height < r1.y
);
}
And from here, you can try to understand the functionality. Though it's in nuxt.js but the events and scripts would be almost same if you are using only javascript. You can find sample code with an explanation for replacing the location of two shapes with each other. So even if you don't want to replace the locations but you want to move your target element to any position this will make you understand how to do this.
I am right now using Ol3 to display static image(like http://openlayers.org/en/latest/examples/static-image.html) . Although when I am zoomed in and start panning/dragging i can see white space as seen in example. I don't want to see that.
Is it possible to pan only till the end of image so that white space does not appear?
You can restrict the dragging of the image by restricting its extent.
Here's an inline link to fiddle.
map.on('moveend', function(evt){
console.log(view.getZoom());
if(view.getZoom()<=2){
view.setZoom(3);
}
});
var constrainPan = function() {
console.log("move");
var extents=[0, 0, 1024, 968]
var visible = view.calculateExtent(map.getSize());
var centre = view.getCenter();
var delta;
var adjust = false;
if ((delta = extents[0] - visible[0]) > 0) {
adjust = true;
//console.log(delta);
centre[0] += delta;
} else if ((delta = extents[2] - visible[2]) < 0) {
adjust = true;
centre[0] += delta;
}
if ((delta = extents[1] - visible[1]) > 0) {
adjust = true;
centre[1] += delta;
} else if ((delta = extents[3] - visible[3]) < 0) {
adjust = true;
centre[1] += delta;
}
if (adjust) {
view.setCenter(centre);
}
};
view.on('change:resolution', constrainPan);
view.on('change:center', constrainPan);
I have rectangle WMS of small area and want to restrict panning outside WMS extends, so there aren't white or black area outside the map visible at all.
Adding extent to View does not work for me and in documentation about this option is written
The extent that constrains the center, in other words, center cannot
be set outside this extent.
But as I understand this if center is in the area of extent, but on the very corner, it will show white area outside this extent, but I don't want to see white area at all.
Is it possible to achieve this with OL3?
Here's my solution. I wrote it just now, and so it is not extensively tested. It would probably break if you start rotating the map, for example, and it may be glitchy if you zoom out too far.
var constrainPan = function() {
var visible = view.calculateExtent(map.getSize());
var centre = view.getCenter();
var delta;
var adjust = false;
if ((delta = extent[0] - visible[0]) > 0) {
adjust = true;
centre[0] += delta;
} else if ((delta = extent[2] - visible[2]) < 0) {
adjust = true;
centre[0] += delta;
}
if ((delta = extent[1] - visible[1]) > 0) {
adjust = true;
centre[1] += delta;
} else if ((delta = extent[3] - visible[3]) < 0) {
adjust = true;
centre[1] += delta;
}
if (adjust) {
view.setCenter(centre);
}
};
view.on('change:resolution', constrainPan);
view.on('change:center', constrainPan);
This expects the variables map, view (with obvious meanings) and extent (the xmin, ymin, xmax, ymax you want to be visible) to be available.
Here's a more robust implementation that should work really well in any case. It's written in ES6, and requires isEqual method (from lodash or anything else ...)
const extent = [-357823.2365, 6037008.6939, 1313632.3628, 7230727.3772];
const view = this.olMap.getView();
const modifyValues = {};
// Trick to forbid panning outside extent
let constrainPan = (e) => {
const type = e.type;
const newValue = e.target.get(e.key);
const oldValue = e.oldValue;
if (isEqual(oldValue, newValue)) {
// Do nothing when event doesn't change the value
return;
}
if (isEqual(modifyValues[type], newValue)) {
// Break possible infinite loop
delete modifyValues[type];
return;
}
if (type === 'change:resolution' && newValue < oldValue) {
// Always allow zoom-in.
return;
}
const visibleExtent = view.calculateExtent(this.olMap.getSize());
const intersection = ol.extent.getIntersection(visibleExtent, extent);
const modify = !isEqual(intersection, visibleExtent);
if (modify) {
if (type === 'change:center') {
const newCenter = newValue.slice(0);
if (ol.extent.getWidth(visibleExtent) !== ol.extent.getWidth(intersection)) {
newCenter[0] = oldValue[0];
}
if (ol.extent.getHeight(visibleExtent) !== ol.extent.getHeight(intersection)) {
newCenter[1] = oldValue[1];
}
modifyValues[type] = newCenter;
view.setCenter(newCenter);
} else if (type === 'change:resolution') {
modifyValues[type] = oldValue;
view.setResolution(oldValue);
}
}
};
view.on('change:resolution', constrainPan);
view.on('change:center', constrainPan);
This is an extension to #tremby answer, but to long for a comment.
First of all, his solution works really well for me, but it was called way to often. Therefore I wrapped it in a debounce function.
So
view.on('change:resolution', constrainPan);
view.on('change:center', constrainPan);
becomes
var dConstrainPan = debounce(constrainPan);
view.on('change:resolution', dConstrainPan);
view.on('change:center', dConstrainPan);
This will result in a slight flicker, when moving outside the bounding box, bot zooming/ moving works without delay.
Still not perfect but a useful improvement from my point of view.
Debounce code:
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
Soruce: https://davidwalsh.name/javascript-debounce-function , in underscore.js
The focus moves to the next input field before the event is fired. Can anyone help me find the bug, or figure out how to find it myself?
The goal is to catch the keyup event, verify that it is tab or shift+tab, and then tab as though it were tabbing through a table. When the focus gets to the last input that is visible, the three rows (see fiddle for visual) should move together to reveal hidden inputs. Once to the end of the inputs in that row, the three rows will slide back down to the beginning again, kind of like a carriage return on a typewriter, or tabbing into a different row in a table.
Right now, the tab event is moving just the row that holds the focus, and it is moving it before my script even starts to run. I just need to know why this is happening so that I can research how to resolve it.
Any help you can offer is appreciated. Please let me know if you need more information.
P.S. Using jquery 1.9.1
Link to Fiddle
jQuery(document).ready(function ($) {
// bind listeners to time input fields
//$('.timeBlock').blur(validateHrs);
$('.timeBlock').keyup(function () {
var caller = $(this);
var obj = new LayoutObj(caller);
if (event.keyCode === 9) {
if (event.shiftKey) {
obj.dir = 'prev';
}
obj.navDates();
}
});
// bind listeners to prev/next buttons
$('.previous, .next').on('click', function () {
var str = $(this).attr('class');
var caller = $(this);
var obj = new LayoutObj(caller);
obj.src = 'pg';
if (str === 'previous') {
obj.dir = 'prev';
}
obj.navDates();
});
});
function LayoutObj(input) {
var today = new Date();
var thisMonth = today.getMonth();
var thisDate = today.getDate();
var dateStr = '';
var fullDates = $('.dateNum');
var splitDates = new Array();
this.currIndex = 0; //currIndex defaults to 0
this.todayIndex;
fullDates.each(function (index) {
splitDates[index] = $(this).text().split('/');
});
//traverse the list of dates in the pay period, compare values and stop when/if you find today
for (var i = 0; i < splitDates.length; i++) {
if (thisMonth === (parseInt(splitDates[i][0], 10) - 1) && thisDate === parseInt(splitDates[i][1], 10)) {
thisMonth += 1;
thisMonth += '';
thisDate += '';
if (thisMonth.length < 2) {
dateStr = "0" + thisMonth + "/";
}
else {
dateStr = thisMonth + "/";
}
if (thisDate.length < 2) {
dateStr += "0" + thisDate;
}
else {
dateStr += thisDate;
}
fullDates[i].parentNode.setAttribute('class', 'date today');
this.todayIndex = i;
break;
}
}
//grab all of the lists & the inputs
this.window = $('div.timeViewList');
this.allLists = $('.timeViewList ul');
this.inputs = $('.timeBlock');
//if input`isn't null, set currIndex to match index of caller
if (input !== null) {
this.currIndex = this.inputs.index(input);
}
//else if today is in the pay period, set currIndex to todayIndex
else if (this.todayIndex !== undefined) {
this.currIndex = this.todayIndex;
}
//(else default = 0)
//grab the offsets for the cell, parent, and lists.
this.winOffset = this.window.offset().left;
this.cellOffset = this.inputs.eq(this.currIndex).offset().left;
this.listOffset = this.inputs.offset().left;
//grab the width of a cell, the parent, and lists
this.cellWidth = this.inputs.outerWidth();
this.listWidth = this.inputs.last().offset().left + this.cellWidth - this.inputs.eq(0).offset().left;
this.winWidth = this.window.outerWidth();
//calculate the maximum (left) offset between the lists and the parents
this.offsetMax = (this.listWidth - this.winWidth);
//set default scroll direction as fwd, and default nav as tab
this.dir = 'next';
this.src = 'tab';
//grab the offsets for the cell, parent, and lists.
this.cellOffset = this.inputs.eq(this.currIndex).offset().left;
this.listOffset = this.inputs.eq(0).offset().left;
this.winOffset = this.allLists.parent().offset().left;
//calculate the maximum (left) offset between the lists and the parents
this.offsetMax = (this.listWidth - this.winWidth);
}
LayoutObj.prototype.focusDate = function () {
this.inputs.eq(this.currIndex).focus();
};
LayoutObj.prototype.slideLists = function (num) {
this.listOffset += num;
this.allLists.offset({ left: this.listOffset });
};
LayoutObj.prototype.navDates = function () {
if (!this.inWindow()) {
var slide = 0;
switch (this.src) {
case 'pg':
slide = this.winWidth - this.cellWidth;
break;
case 'tab':
slide = this.cellWidth + 1;
break;
default:
break;
}
if (this.dir === 'next') {
slide = -slide;
}
this.slideLists(slide);
}
this.focusDate();
};
LayoutObj.prototype.inWindow = function () {
//detects if cell intended for focus is visible in the parent div
if ((this.cellOffset > this.winOffset) && ((this.cellOffset + this.cellWidth) < (this.winOffset + this.winWidth))) {
return true;
}
else {
return false;
}
}
All it needed was 'keydown()' instead of 'keyup().'
I'm currently using the following code to scroll up/down text... I'd like to make the following modifications to the code, but am struggling on how to achieve it:
Check the location of the text and affect the opacity of the
up/down arrows. When the content of #scroll is at the top, the up (#scroll-up) arrow would be faded back. When the content of #scroll is at the bottom, the down (#scroll-down) arrow be faded back. Every where in between, both button's would be faded in.
Hide the scroll buttons if they are not needed
Here's the current code:
var ele = $('#scroll');
var speed = 25, scroll = 5, scrolling;
$('#scroll-up').mouseenter(function() {
scrolling = window.setInterval(function() {
ele.scrollTop( ele.scrollTop() - scroll );
}, speed);
});
$('#scroll-down').mouseenter(function() {
scrolling = window.setInterval(function() {
ele.scrollTop( ele.scrollTop() + scroll );
}, speed);
});
$('#scroll-up, #scroll-down').bind({
click: function(e) {
e.preventDefault();
},
mouseleave: function() {
if (scrolling) {
window.clearInterval(scrolling);
scrolling = false;
}
}
});
Looks like this might do the trick.
$(function() {
var ele = $('#scroll');
var speed = 25, scroll = 5, scrolling;
$("#scroll-up").css('opacity', 0.5);
$('#scroll-up').mouseenter(function() {
// Scroll the element up
scrolling = window.setInterval(function() {
ele.scrollTop( ele.scrollTop() - scroll );
$("#scroll-up").css("opacity", (ele.scrollTop() == 0) ? 0.5 : 1);
$("#scroll-down").css("opacity", (ele[0].scrollHeight - ele.scrollTop() == ele.outerHeight()) ? 0.5 : 1);
}, speed);
});
$('#scroll-down').mouseenter(function() {
// Scroll the element down
scrolling = window.setInterval(function() {
ele.scrollTop( ele.scrollTop() + scroll );
$("#scroll-up").css("opacity", (ele.scrollTop() == 0) ? 0.5 : 1);
$("#scroll-down").css("opacity", (ele[0].scrollHeight - ele.scrollTop() == ele.outerHeight()) ? 0.5 : 1);
}, speed);
});
$('#scroll-up, #scroll-down').bind({
click: function(e) {
// Prevent the default click action
e.preventDefault();
},
mouseleave: function() {
if (scrolling) {
window.clearInterval(scrolling);
scrolling = false;
}
}
});
var winHeight = $(window).height();
$("#scroll").css("height", winHeight - 220);
if (ele[0].scrollHeight == ele.outerHeight()){
$("#contentscrollnav").hide();
}
});
$(window).resize(function() {
var winHeight = $(window).height();
$("#scroll").css("height", winHeight - 220);
var ele = $('#scroll');
if (ele[0].scrollHeight <= ele.outerHeight()){
$("#contentscrollnav").hide();
} else if (ele[0].scrollHeight > ele.outerHeight()) {
$("#contentscrollnav").show();
}
});
You can see the code in action at:
http://jsfiddle.net/MzDst/11/