I am trying to implement custom draggable directive in angularjs but it trows
Range error . what is wrong in this code
(function (window, angular, undefined) {
var app = angular.module('ngDraggableModule', []);
app.directive('easyDrag', ['$document', function ($document) {
return {
restrict: 'EA',
scope: {
params: '=',
elemId: '='
},
link: function (scope, elem, attrs) {
var isMouseDown = false,
startDrag = false,
position = {},
x = 0,
y = 0,
startX = 0,
startY = 0,
changedPos = 0;
elem.bind('mousedown', onMouseDown);
function onMouseDown(e) {
if (angular.isDefined(scope.params.disabled) && scope.params.disabled === true) {
return false;
}
isMouseDown = true;
startX = e.screenX - x;
startY = e.screenY - y;
$document.bind('mousemove', onMouseMove);
$document.bind('mouseup', onMouseUp);
}
function onMouseMove(e) {
if (isMouseDown) {
if (!startDrag) {
startDrag = true;
if (angular.isFunction(scope.params.start)) {
scope.params.start(changedPos, e, scope.elemId);
}
}
y = e.screenY - startY;
x = e.screenX - startX;
position = {};
if (angular.isDefined(scope.params.axis)) {
if (scope.params.axis.toLowerCase() == 'x') {
position.marginLeft = x + 'px';
}
else if (scope.params.axis.toLowerCase() == 'y') {
position.marginTop = y + 'px';
}
} else {
position.marginTop = y + 'px';
position.marginLeft = x + 'px';
}
changedPos = position;
elem.css(position);
if (angular.isFunction(scope.params.drag)) {
scope.params.drag(e, changedPos, scope.elemId);
}
}
}
function onMouseUp(e) {
if (!isMouseDown) {
return false;
}
isMouseDown = false;
startDrag = false;
if (angular.isFunction(scope.params.stop)) {
scope.params.stop(changedPos, e, scope.elemId);
}
}
}
}
}]);
})(window, window.angular, undefined);
var app = angular.module('DemoApp',['ngDraggableModule']);
app.controller('MainCtrl',['$scope',function($scope){
$scope.assetDragParams = {
drag : function(e,pos,id){
console.log(id); // here is the error
}
}
}])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="MainCtrl">
<div easy-drag params="assetDragParams" elem-id="col._id">
drag it
</div>
</div>
calling directive such this trows RangeError : Maximum call stack size exceeded
what is wrong ? thanks in advance.
You need to bind the mousemove + mouseup outside of mousedown, anyway you set isMouseDown = true in the onMouseDown function. All the function binding should be like this:
elem.bind('mousedown', onMouseDown);
$document.bind('mousemove', onMouseMove);
$document.bind('mouseup', onMouseUp);
And onMouseDown function should be:
function onMouseDown(e) {
if (angular.isDefined(scope.params.disabled) && scope.params.disabled === true) {
return false;
}
isMouseDown = true;
startX = e.screenX - x;
startY = e.screenY - y;
}
Related
I'm trying to follow along with this article to make an item move anywhere on the screen by dragging it: https://www.kirupa.com/html5/drag.htm The thing is that the article is in regular JavaScript and I'm using Vue so the way event handlers get handled are slightly different. For some reason, #dragStart and #drag don't fire at all but #dragEnd does fire when the user stops dragging the item.
Only div that I'm using for this
<div class="pip" draggable="true" #dragStart="dragStart" #drag="drag" #dragend="dragEnd">
Script tag
data() {
return {
active: false,
currentX: null,
currentY: null,
initialX: null,
initialY: null,
xOffset: 0,
yOffset: 0,
};
},
methods: {
dragStart(event) {
console.log('start');
const dragItem = document.querySelector('.pip');
if (event.type === 'touchstart') {
this.initialX = event.touches[0].clientX - this.xOffset;
this.initialY = event.touches[0].clientY - this.yOffset;
} else {
this.initialX = event.clientX - this.xOffset;
this.initialY = event.clientY - this.yOffset;
}
if (event.target === dragItem) {
this.active = true;
}
},
drag(event) {
console.log('drag');
const dragItem = document.querySelector('.pip');
if (this.active) {
event.preventDefault();
if (event.type === 'touchmove') {
this.currentX = event.touches[0].clientX - this.initialX;
this.currentY = event.touches[0].clientY - this.initialY;
} else {
this.currentX = event.clientX - this.initialX;
this.currentY = event.clientY - this.initialY;
}
this.xOffset = this.currentX;
this.yOffset = this.currentY;
this.setTranslate(this.currentX, this.currentY, dragItem);
}
},
dragEnd() {
console.log('end');
this.initialX = this.currentX;
this.initialY = this.currentY;
this.active = false;
// event.target.style.bottom = '0px';
// event.target.style.top = `${event.clientY - event.target.offsetHeight / 2}px`;
// event.target.style.left = `${event.clientX - event.target.offsetWidth / 2}px`;
// console.log(event);
},
setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(' + xPos + 'px, ' + yPos + 'px, 0)";
},
So i am trying to implement simple touch controls on a javascript game. I have the following answer from a search:
Snake Game with Controller Buttons for Mobile Use **UPDATED**
However I was trying to change this jquery into javascript so that it would work with my game
Jquery:
$(document).on('click', '.button-pad > button', function(e) {
if ($(this).hasClass('left-btn')) {
e = 37;
}
Javascript:
var contoller = document.getElementById("button-pad").on('click',
'.button-pad > button', function(e) {
if ('.button-pad > button'(this).hasClass('btn-left')) {
e = 37;
}
I thought I had it sorted but it is not working at all
Codepen here:
https://codepen.io/MrVincentRyan/pen/VqpMrJ?editors=1010
Your existing code has some problems with it, but it was close enough where I could translate it. However, your current code seems to want to reassign the event argument being passed to the click handler (e) to 37. This makes no sense. Most likely you just want another variable set to 37 and that's what I've done below:
spaceInvader(window, document.getElementById('space-invader'));
window.focus();
let game = null;
let ship = null;
function spaceInvader (window, canvas) {
canvas.focus();
var context = canvas.getContext('2d');
/* GAME */
function Game () {
this.message = '';
this.rebel = [];
this.republic = [];
this.other = [];
this.size = {x: canvas.width, y: canvas.height};
this.wave = 0;
this.refresh = function () {
this.update();
this.draw();
requestAnimationFrame(this.refresh);
}.bind(this);
this.init();
}
Game.MESSAGE_DURATION = 1500;
Game.prototype.init = function () {
this.ship = new Ship(this);
this.addRebel(this.ship);
this.refresh();
};
Game.prototype.update = function () {
this.handleCollisions();
this.computeElements();
this.elements.forEach(Element.update);
if (!this.rebel.length) {
this.showText('Gatwick closed', true);
return;
}
if (!this.republic.length) this.createWave();
};
Game.prototype.draw = function () {
context.clearRect(0, 0, this.size.x, this.size.y);
this.elements.forEach(Element.draw);
Alien.drawLife(this.republic);
if (this.message) {
context.save();
context.font = '30px Arial';
context.textAlign='center';
context.fillStyle = '#FFFFFF';
context.fillText(this.message, canvas.width / 2, canvas.height / 2);
context.restore();
}
};
Game.prototype.computeElements = function () {
this.elements = this.other.concat(this.republic, this.rebel);
};
Game.prototype.addRebel = function (element) {
this.rebel.push(element);
};
Game.prototype.addRepublic = function (element) {
this.republic.push(element);
};
Game.prototype.addOther = function (element) {
this.other.push(element);
};
Game.prototype.handleCollisions = function () {
this.rebel.forEach(function(elementA) {
this.republic.forEach(function (elementB) {
if (!Element.colliding(elementA, elementB)) return;
elementA.life--;
elementB.life--;
var sizeA = elementA.size.x * elementA.size.y;
var sizeB = elementB.size.x * elementB.size.y;
this.addOther(new Explosion(this, sizeA > sizeB ? elementA.pos : elementB.pos));
}, this);
}, this);
this.republic = this.republic.filter(Element.isAlive);
this.rebel = this.rebel.filter(Element.isAlive);
this.other = this.other.filter(Element.isAlive);
this.republic = this.republic.filter(this.elementInGame, this);
this.rebel = this.rebel.filter(this.elementInGame, this);
};
Game.prototype.elementInGame = function (element) {
return !(element instanceof Bullet) || (
element.pos.x + element.halfWidth > 0 &&
element.pos.x - element.halfWidth < this.size.x &&
element.pos.y + element.halfHeight > 0 &&
element.pos.y - element.halfHeight < this.size.x
);
};
Game.prototype.createWave = function () {
this.ship.life = Ship.MAX_LIFE;
this.ship.fireRate = Math.max(50, Ship.FIRE_RATE - 50 * this.wave);
this.wave++;
this.showText('Wave: ' + this.wave);
var waveSpeed = Math.ceil(this.wave / 2);
var waveProb = (999 - this.wave * 2) / 1000;
var margin = {x: Alien.SIZE.x + 10, y: Alien.SIZE.y + 10};
for (var i = 0; i < 2; i++) {
var x = margin.x + (i % 8) * margin.x;
var y = -200 + (i % 3) * margin.y;
this.addRepublic(new Alien(this, {x: x, y: y}, waveSpeed, waveProb));
}
};
Game.prototype.showText = function (message, final) {
this.message = message;
if (!final) setTimeout(this.showText.bind(this, '', true), Game.MESSAGE_DURATION);
};
/* GENERIC ELEMENT */
function Element (game, pos, size) {
this.game = game;
this.pos = pos;
this.size = size;
this.halfWidth = Math.floor(this.size.x / 2);
this.halfHeight = Math.floor(this.size.y / 2);
}
Element.update = function (element) {
element.update();
};
Element.draw = function (element) {
element.draw();
};
Element.isAlive = function (element) {
return element.life > 0;
};
Element.colliding = function (elementA, elementB) {
return !(
elementA === elementB ||
elementA.pos.x + elementA.halfWidth < elementB.pos.x - elementB.halfWidth ||
elementA.pos.y + elementA.halfHeight < elementB.pos.y - elementB.halfHeight ||
elementA.pos.x - elementA.halfWidth > elementB.pos.x + elementB.halfWidth ||
elementA.pos.y - elementA.halfHeight > elementB.pos.y + elementB.halfHeight
);
};
/* SHIP */
function Ship(game) {
var pos = {
x: Math.floor(game.size.x / 2) - Math.floor(Ship.SIZE.x / 2),
y: game.size.y - Math.floor(Ship.SIZE.y / 2)
};
Element.call(this, game, pos, Ship.SIZE);
this.kb = new KeyBoard();
this.speed = Ship.SPEED;
this.allowShooting = true;
this.life = Ship.MAX_LIFE;
this.fireRate = Ship.FIRE_RATE;
}
Ship.SIZE = {x: 67, y: 100};
Ship.SPEED = 8;
Ship.MAX_LIFE = 5;
Ship.FIRE_RATE = 200;
Ship.prototype.update = function () {
if (this.kb.isDown(KeyBoard.KEYS.LEFT) && this.pos.x - this.halfWidth > 0) {
this.pos.x -= this.speed;
} else if (this.kb.isDown(KeyBoard.KEYS.RIGHT) && this.pos.x + this.halfWidth < this.game.size.x) {
this.pos.x += this.speed;
}
if (this.allowShooting && this.kb.isDown(KeyBoard.KEYS.SPACE)) {
var bullet = new Bullet(
this.game,
{x: this.pos.x, y: this.pos.y - this.halfHeight },
{ x: 0, y: -Bullet.SPEED },
true
);
this.game.addRebel(bullet);
this.toogleShooting();
}
};
Ship.prototype.draw = function () {
var img = document.getElementById('ship');
context.save();
context.translate(this.pos.x - this.halfWidth, this.pos.y - this.halfHeight);
context.drawImage(img, 0, 0);
context.restore();
this.drawLife();
};
Ship.prototype.drawLife = function () {
context.save();
context.fillStyle = 'white';
context.fillRect(this.game.size.x -112, 10, 102, 12);
context.fillStyle = 'red';
context.fillRect(this.game.size.x -111, 11, this.life * 100 / Ship.MAX_LIFE, 10);
context.restore();
};
Ship.prototype.toogleShooting = function (final) {
this.allowShooting = !this.allowShooting;
if (!final) setTimeout(this.toogleShooting.bind(this, true), this.fireRate);
};
/* ALIENS */
function Alien(game, pos, speed, shootProb) {
Element.call(this, game, pos, Alien.SIZE);
this.speed = speed;
this.shootProb = shootProb;
this.life = 3;
this.direction = {x: 1, y: 1};
}
Alien.SIZE = {x: 51, y: 60};
Alien.MAX_RANGE = 350;
Alien.CHDIR_PRO = 0.990;
Alien.drawLife = function (array) {
array = array.filter(function (element) {
return element instanceof Alien;
});
context.save();
context.fillStyle = 'white';
context.fillRect(10, 10, 10 * array.length + 2, 12);
array.forEach(function (alien, idx) {
switch (alien.life) {
case 3:
context.fillStyle = 'green';
break;
case 2:
context.fillStyle = 'yellow';
break;
case 1:
context.fillStyle = 'red';
break;
}
context.fillRect(10 * idx + 11, 11, 10, 10);
});
context.restore();
};
Alien.prototype.update = function () {
if (this.pos.x - this.halfWidth <= 0) {
this.direction.x = 1;
} else if (this.pos.x + this.halfWidth >= this.game.size.x) {
this.direction.x = -1;
} else if (Math.random() > Alien.CHDIR_PRO) {
this.direction.x = -this.direction.x;
}
if (this.pos.y - this.halfHeight <= 0) {
this.direction.y = 1;
} else if (this.pos.y + this.halfHeight >= Alien.MAX_RANGE) {
this.direction.y = -1;
} else if (Math.random() > Alien.CHDIR_PRO) {
this.direction.y = -this.direction.y;
}
this.pos.x += this.speed * this.direction.x;
this.pos.y += this.speed * this.direction.y;
if (Math.random() > this.shootProb) {
var bullet = new Bullet(
this.game,
{x: this.pos.x, y: this.pos.y + this.halfHeight },
{ x: Math.random() - 0.5, y: Bullet.SPEED },
false
);
this.game.addRepublic(bullet);
}
};
Alien.prototype.draw = function () {
var img = document.getElementById('fighter');
context.save();
context.translate(this.pos.x + this.halfWidth, this.pos.y + this.halfHeight);
context.rotate(Math.PI);
context.drawImage(img, 0, 0);
context.restore();
};
/* BULLET */
function Bullet(game, pos, direction, isRebel) {
Element.call(this, game, pos, Bullet.SIZE);
this.direction = direction;
this.isRebel = isRebel;
this.life = 1;
try {
var sound = document.getElementById('sound-raygun');
sound.load();
sound.play().then(function () {}, function () {});
}
catch (e) {
// only a sound issue
}
}
Bullet.SIZE = {x: 6, y: 20};
Bullet.SPEED = 3;
Bullet.prototype.update = function () {
this.pos.x += this.direction.x;
this.pos.y += this.direction.y;
};
Bullet.prototype.draw = function () {
context.save();
var img;
if (this.isRebel) {
context.translate(this.pos.x - this.halfWidth, this.pos.y - this.halfHeight);
img = document.getElementById('rebel-bullet');
}
else {
context.translate(this.pos.x + this.halfWidth, this.pos.y + this.halfHeight);
img = document.getElementById('republic-bullet');
context.rotate(Math.PI);
}
context.drawImage(img, 0, 0);
context.restore();
};
/* EXPLOSION */
function Explosion(game, pos) {
Element.call(this, game, pos, Explosion.SIZE);
this.life = 1;
this.date = new Date();
try {
var sound = document.getElementById('sound-explosion');
sound.load();
sound.play().then(function () {}, function () {});
}
catch (e) {
// only a sound issue
}
}
Explosion.SIZE = {x: 115, y: 100};
Explosion.DURATION = 150;
Explosion.prototype.update = function () {
if (new Date() - this.date > Explosion.DURATION) this.life = 0;
};
Explosion.prototype.draw = function () {
var img = document.getElementById('explosion');
context.save();
context.translate(this.pos.x - this.halfWidth, this.pos.y - this.halfHeight);
context.drawImage(img, 0, 0);
context.restore();
};
/* KEYBOARD HANDLING */
function KeyBoard() {
var state = {};
window.addEventListener('keydown', function(e) {
state[e.keyCode] = true;
});
window.addEventListener('keyup', function(e) {
state[e.keyCode] = false;
});
this.isDown = function (key) {
return state[key];
};
}
KeyBoard.KEYS = {
LEFT: 37,
RIGHT: 39,
SPACE: 32
};
window.addEventListener('load', function() {
game = new Game();
});
// Get all the button elements that are children of elements that have
// the .button-pad class and convert the resulting node list into an Array
let elements =
Array.prototype.slice.call(document.querySelectorAll('.button-pad button'));
// Loop over the array
elements.forEach(function(el){
el.textContent = "XXXX";
// Set up a click event handler for the current element being iterated:
el.addEventListener('click', function(e) {
// When the element is clicked, check to see if it uses the left-btn class
if(this.classList.contains('left-btn')) {
// Perform whatever actions you need to:
ship.update();
}
});
});
}
<h1>Gatwick invaders</h1>
<p>Press <b>left arrow</b> to go left, <b>right arrow</b> to go right, and <b>space</b> to shoot...</p>
<canvas id="space-invader" width="640" height="500" tabindex="0"></canvas>
<img id="fighter" src="https://raw.githubusercontent.com/MrVIncentRyan/assets/master/drone1.png" />
<img id="ship" src="https://raw.githubusercontent.com/MrVIncentRyan/assets/master/cop1.png" />
<img id="rebel-bullet" src="https://raw.githubusercontent.com/OlivierB-OB/starwars-invader/master/rebelBullet.png" />
<img id="republic-bullet" src="https://raw.githubusercontent.com/OlivierB-OB/starwars-invader/master/republicBullet.png" />
<img id="explosion" src="https://raw.githubusercontent.com/OlivierB-OB/starwars-invader/master/explosion.png" />
<audio id="sound-explosion" src="https://raw.githubusercontent.com/OlivierB-OB/starwars-invader/master/explosion.mp3"></audio>
<audio id="sound-raygun" src="https://raw.githubusercontent.com/OlivierB-OB/starwars-invader/master/raygun.mp3"></audio>
</div>
<div class="button-pad">
<div class="btn-up">
<button type="submit" class="up">
<img src="http://aaronblomberg.com/sites/ez/images/btn-up.png" />
</button>
</div>
<div class="btn-right">
<button type="submit" class="right">
<img src="http://aaronblomberg.com/sites/ez/images/btn-right.png" />
</button>
</div>
<div class="btn-down">
<button type="submit" class="down">
<img src="http://aaronblomberg.com/sites/ez/images/btn-down.png" />
</button>
</div>
<div class="btn-left">
<button type="submit" class="left">
<img src="http://aaronblomberg.com/sites/ez/images/btn-left.png" />
</button>
</div>
</div>
A custom solution for emulating keypresses on mobile in both vanilla Javascript as well as jQuery!
// jQuery (edge), for use with ES2015-19
/*
$(document).on("click", ".example-btn", e => { // Click event handler
if($(this).hasClass("example-btn")) { // Verifying that element has class
e = 37
jQuery.event.trigger({type: "keypress", which: character.charCodeAt(e)}) // Simulating keystroke
// The following is simply for debugging, remove if needed
alert("Button validation confirmed!")
console.log("E: ", e)
}
})
*/
// Pure Javascript (ECMA Standard)
document.querySelector(".example-btn").addEventListener("click", function(e) { // Click event handler
if(this.classList.contains("example-btn")) { // Verifying that element has class
e = 37
if(document.createEventObject) {
var eventObj = document.createEventObject();
eventObj.keyCode = e;
document.querySelector(".example-btn").fireEvent("onkeydown", eventObj);
} else if(document.createEvent) {
var eventObj2 = document.createEvent("Events");
eventObj2.initEvent("keydown", true, true);
eventObj2.which = e;
document.querySelector(".example-btn").dispatchEvent(eventObj2);
}
// The following is simply for debugging, remove if needed
alert("Button validation confirmed!");
console.log("E: ", e);
}
});
// ---------------------------------------------------------------------------------------------------
/*
You can not use the "this" statement when referring to an embedded element. In your previous code "this" would refer to ".button-container > .example-btn" which the compiler will interpret as only the parent element, being .button-container (.button-pad in your code) not the child element in which you want. Also there is no such thing as returning a character code and expecting it to automatically know what to do with it. I assume you are doing this to emulate a keystroke on a mobile device and I assure you that this design works although it might be flawed. Give it a try and I hope it does something to at least help if not solve your problem.
*/
// ---------------------------------------------------------------------------------------------------
When an event listener is attached to an element, that listener is not unique for the element, but it propagates to its children.
This functionality is enabled in jQuery by adding a parameter on an event listener a parameter that targets the element that we want.
This is not case in vanillaJS, but using e.target we can inspect in which elements the event is executed.
Probably your are looking something like this. However, I would prefer to add an id in the button so you can more easily work with it.
document.addEventListener('click', function(e){
if(e.target.tagName === 'BUTTON' && e.target.classList.value.includes('btn-left')){
// execute your code
}
});
Trying to create a slider class that lets you easily and quickly customize values. You can drag the handle or click on any part of the slider to move the handle there, and it works... sort of. You can click as many times as you want, but... if you even click once or if you drag, then let go and try again, it only goes down (never up) a tiny bit. That probably sounds confusing, so here's the fiddle.
var Slider = createEntity({
init: function (args) {
args = args || {};
this.x = args.x || 0;
this.y = args.y || 0;
this.width = args.width || 10;
this.height = args.height || 100;
this.min = args.min || 0;
this.max = args.max || 100;
this.value = args.value || 50;
this.rotation = args.rotation || 0;
this.on = args.on || function () {};
var backFill = randHsla();
args.back = args.back || {};
this.back = args.back;
args.back.fill = args.back.fill || backFill;
this.back.fill = args.back.fill;
args.back.borderFill = args.back.borderFill || backFill;
this.back.borderFill = args.back.borderFill;
args.back.width = args.back.width || 5;
this.back.width = args.back.width;
args.back.x = args.back.x || this.width / 2 - this.back.width / 2;
this.back.x = args.back.x;
var handleColor = randHsla();
args.handle = args.handle || {};
this.handle = args.handle;
args.handle.fill = args.handle.fill || handleColor;
this.handle.fill = args.handle.fill;
args.handle.borderStroke = args.handle.borderStroke || handleColor;
this.handle.borderStroke = args.handle.borderStroke;
args.handle.height = args.handle.height || 5;
this.handle.height = args.handle.height;
args.handle.y = args.handle.y || 0;
this.handle.y = args.handle.y;
this.updatePos();
},
draw: function (fx) {
fx.save();
fx.translate(this.x, this.y);
fx.rotate(this.rotation);
fx.fillStyle = this.back.fill;
fx.beginPath();
fx.fillRect(this.back.x, 0, this.back.width, this.height);
fx.closePath();
fx.fillStyle = this.handle.fill;
fx.strokeStyle = this.handle.borderStroke;
fx.beginPath();
fx.rect(0, this.handle.y, this.width, this.handle.height);
fx.closePath();
fx.fill();
fx.stroke();
fx.restore();
},
updateVal: function () {
var oldVal = this.value,
handleRange = this.height - this.handle.height,
valRange = this.max - this.min;
this.value = (handleRange - this.handle.y) / handleRange * valRange + this.min;
if (this.on instanceof Function && this.value !== oldVal) {
this.on();
}
return this;
},
updatePos: function () {
var handleRange = this.height - this.handle.height,
valRange = this.max - this.min;
this.handle.y = handleRange - ((this.value - this.min) / valRange) * handleRange;
return this;
},
getMouse: function (map) {
var self = this,
mouse = getMouse(map),
bounds = {};
setBounds();
map.addEventListener('mousedown', function (event) {
if (hasPoint(bounds, mouse.x, mouse.y)) {
map.addEventListener('mousemove', onMouseMove);
map.addEventListener('mouseup', onMouseUp);
} else if (hasPoint(self, mouse.x, mouse.y)) {
var y = mouse.y - self.y;
self.handle.y = Math.min(self.height - self.handle.height, Math.max(y, 0));
self.updateVal();
}
});
function onMouseUp(event) {
map.removeEventListener('mousemove', onMouseMove, false);
map.removeEventListener('mouseup', onMouseUp, false);
}
function onMouseMove(event) {
var y = mouse.y - self.y;
self.handle.y = Math.min(self.height - self.handle.height, Math.max(y, 0));
self.updateVal();
}
function setBounds() {
bounds.x = self.x;
bounds.y = self.y + self.handle.y;
bounds.width = self.width;
bounds.height = self.handle.height;
}
return this;
}
});
External functions such as createEntity and hasPoint can be found here.
How would I make it work after clicking the slider and letting go after the first?
Add the event listeners if the user clicks on the handle as well, to enable dragging.
map.addEventListener('mousedown', function(event) {
if (hasPoint(bounds, mouse.x, mouse.y)) {
map.addEventListener('mousemove', onMouseMove);
map.addEventListener('mouseup', onMouseUp);
} else if (hasPoint(self, mouse.x, mouse.y)) {
map.addEventListener('mousemove', onMouseMove);
map.addEventListener('mouseup', onMouseUp);
var y = mouse.y - self.y;
self.handle.y = Math.min(self.height - self.handle.height, Math.max(y, 0));
self.updateVal();
}
});
I am using angular-dragdrop.js.
URL: http://angular-dragdrop.github.io/angular-dragdrop/ in my project for drag and drop functionality.
I am facing some issue while using Input type text. It is not working in only IE browser. IE 11 and IE 10.
Problem -
onCLick on input box focus is coming but cursor inside the input box is not coming.
HTML Code:
<div ng-repeat="(name, panel) in row.panels"
class="panel"
ui-draggable="true" drag="panel.id"
ui-on-Drop="onDrop($data, row, panel)"
drag-handle-class="drag-handle" panel-width ng-model="panel">
<input type="text"/>
<grafana-panel type="panel.type" ng-cloak></grafana-panel>
</div>
<div ng-repeat="(name, panel) in row.panels"
class="panel"
ui-draggable="false" drag="panel.id"
ui-on-Drop="onDrop($data, row, panel)"
drag-handle-class="drag-handle" panel-width ng-model="panel">
<input type="text"/>
<grafana-panel type="panel.type" ng-cloak></grafana-panel>
</div>
for ui-draggable false it is working. but for ui-draggable true its not working.
Vendor JS file:
/**
* Created with IntelliJ IDEA.
* User: Ganaraj.Pr
* Date: 11/10/13
* Time: 11:27
* To change this template use File | Settings | File Templates.
*/
(function(angular){
function isDnDsSupported(){
return 'ondrag' in document.createElement("a");
}
if(!isDnDsSupported()){
angular.module("ang-drag-drop", []);
return;
}
if (window.jQuery && (-1 == window.jQuery.event.props.indexOf("dataTransfer"))) {
window.jQuery.event.props.push("dataTransfer");
}
var currentData;
angular.module("ang-drag-drop",[])
.directive("uiDraggable", [
'$parse',
'$rootScope',
'$dragImage',
function ($parse, $rootScope, $dragImage) {
return function (scope, element, attrs) {
var dragData = "",
isDragHandleUsed = false,
dragHandleClass,
draggingClass = attrs.draggingClass || "on-dragging",
dragTarget;
element.attr("draggable", false);
attrs.$observe("uiDraggable", function (newValue) {
if(newValue){
element.attr("draggable", newValue);
}
else{
element.removeAttr("draggable");
}
});
if (attrs.drag) {
scope.$watch(attrs.drag, function (newValue) {
dragData = newValue || "";
});
}
if (angular.isString(attrs.dragHandleClass)) {
isDragHandleUsed = true;
dragHandleClass = attrs.dragHandleClass.trim() || "drag-handle";
element.bind("mousedown", function (e) {
dragTarget = e.target;
});
}
function dragendHandler(e) {
setTimeout(function() {
element.unbind('$destroy', dragendHandler);
}, 0);
var sendChannel = attrs.dragChannel || "defaultchannel";
$rootScope.$broadcast("ANGULAR_DRAG_END", sendChannel);
if (e.dataTransfer && e.dataTransfer.dropEffect !== "none") {
if (attrs.onDropSuccess) {
var fn = $parse(attrs.onDropSuccess);
scope.$evalAsync(function () {
fn(scope, {$event: e});
});
} else {
if (attrs.onDropFailure) {
var fn = $parse(attrs.onDropFailure);
scope.$evalAsync(function () {
fn(scope, {$event: e});
});
}
}
}
element.removeClass(draggingClass);
}
element.bind("dragend", dragendHandler);
element.bind("dragstart", function (e) {
var isDragAllowed = !isDragHandleUsed || dragTarget.classList.contains(dragHandleClass);
if (isDragAllowed) {
var sendChannel = attrs.dragChannel || "defaultchannel";
var sendData = angular.toJson({ data: dragData, channel: sendChannel });
var dragImage = attrs.dragImage || null;
element.addClass(draggingClass);
element.bind('$destroy', dragendHandler);
if (dragImage) {
var dragImageFn = $parse(attrs.dragImage);
scope.$evalAsync(function() {
var dragImageParameters = dragImageFn(scope, {$event: e});
if (dragImageParameters) {
if (angular.isString(dragImageParameters)) {
dragImageParameters = $dragImage.generate(dragImageParameters);
}
if (dragImageParameters.image) {
var xOffset = dragImageParameters.xOffset || 0,
yOffset = dragImageParameters.yOffset || 0;
e.dataTransfer.setDragImage(dragImageParameters.image, xOffset, yOffset);
}
}
});
}
e.dataTransfer.setData("Text", sendData);
currentData = angular.fromJson(sendData);
e.dataTransfer.effectAllowed = "copyMove";
$rootScope.$broadcast("ANGULAR_DRAG_START", sendChannel, currentData.data);
}
else {
e.preventDefault();
}
});
};
}
])
.directive("uiOnDrop", [
'$parse',
'$rootScope',
function ($parse, $rootScope) {
return function (scope, element, attr) {
var dragging = 0; //Ref. http://stackoverflow.com/a/10906204
var dropChannel = attr.dropChannel || "defaultchannel" ;
var dragChannel = "";
var dragEnterClass = attr.dragEnterClass || "on-drag-enter";
var dragHoverClass = attr.dragHoverClass || "on-drag-hover";
var customDragEnterEvent = $parse(attr.onDragEnter);
var customDragLeaveEvent = $parse(attr.onDragLeave);
function onDragOver(e) {
if (e.preventDefault) {
e.preventDefault(); // Necessary. Allows us to drop.
}
if (e.stopPropagation) {
e.stopPropagation();
}
var fn = $parse(attr.uiOnDragOver);
scope.$evalAsync(function () {
fn(scope, {$event: e, $channel: dropChannel});
});
e.dataTransfer.dropEffect = e.shiftKey ? 'copy' : 'move';
return false;
}
function onDragLeave(e) {
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
dragging--;
if (dragging == 0) {
scope.$evalAsync(function () {
customDragEnterEvent(scope, {$event: e});
});
element.removeClass(dragHoverClass);
}
var fn = $parse(attr.uiOnDragLeave);
scope.$evalAsync(function () {
fn(scope, {$event: e, $channel: dropChannel});
});
}
function onDragEnter(e) {
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
dragging++;
var fn = $parse(attr.uiOnDragEnter);
scope.$evalAsync(function () {
fn(scope, {$event: e, $channel: dropChannel});
});
$rootScope.$broadcast("ANGULAR_HOVER", dragChannel);
scope.$evalAsync(function () {
customDragLeaveEvent(scope, {$event: e});
});
element.addClass(dragHoverClass);
}
function onDrop(e) {
if (e.preventDefault) {
e.preventDefault(); // Necessary. Allows us to drop.
}
if (e.stopPropagation) {
e.stopPropagation(); // Necessary. Allows us to drop.
}
var sendData = e.dataTransfer.getData("Text");
sendData = angular.fromJson(sendData);
var fn = $parse(attr.uiOnDrop);
scope.$evalAsync(function () {
fn(scope, {$data: sendData.data, $event: e, $channel: sendData.channel});
});
element.removeClass(dragEnterClass);
dragging = 0;
}
function isDragChannelAccepted(dragChannel, dropChannel) {
if (dropChannel === "*") {
return true;
}
var channelMatchPattern = new RegExp("(\\s|[,])+(" + dragChannel + ")(\\s|[,])+", "i");
return channelMatchPattern.test("," + dropChannel + ",");
}
function preventNativeDnD(e) {
if (e.preventDefault) {
e.preventDefault();
}
if (e.stopPropagation) {
e.stopPropagation();
}
e.dataTransfer.dropEffect = "none";
return false;
}
var deregisterDragStart = $rootScope.$on("ANGULAR_DRAG_START", function (event, channel) {
dragChannel = channel;
if (isDragChannelAccepted(channel, dropChannel)) {
if (attr.dropValidate) {
var validateFn = $parse(attr.dropValidate);
var valid = validateFn(scope, {$data: currentData.data, $channel: currentData.channel});
if (!valid) {
element.bind("dragover", preventNativeDnD);
element.bind("dragenter", preventNativeDnD);
element.bind("dragleave", preventNativeDnD);
element.bind("drop", preventNativeDnD);
return;
}
}
element.bind("dragover", onDragOver);
element.bind("dragenter", onDragEnter);
element.bind("dragleave", onDragLeave);
element.bind("drop", onDrop);
element.addClass(dragEnterClass);
}
else {
element.bind("dragover", preventNativeDnD);
element.bind("dragenter", preventNativeDnD);
element.bind("dragleave", preventNativeDnD);
element.bind("drop", preventNativeDnD);
}
});
var deregisterDragEnd = $rootScope.$on("ANGULAR_DRAG_END", function (e, channel) {
dragChannel = "";
if (isDragChannelAccepted(channel, dropChannel)) {
element.unbind("dragover", onDragOver);
element.unbind("dragenter", onDragEnter);
element.unbind("dragleave", onDragLeave);
element.unbind("drop", onDrop);
element.removeClass(dragHoverClass);
element.removeClass(dragEnterClass);
}
element.unbind("dragover", preventNativeDnD);
element.unbind("dragenter", preventNativeDnD);
element.unbind("dragleave", preventNativeDnD);
element.unbind("drop", preventNativeDnD);
});
var deregisterDragHover = $rootScope.$on("ANGULAR_HOVER", function (e, channel) {
if (isDragChannelAccepted(channel, dropChannel)) {
element.removeClass(dragHoverClass);
}
});
scope.$on('$destroy', function () {
deregisterDragStart();
deregisterDragEnd();
deregisterDragHover();
});
attr.$observe('dropChannel', function (value) {
if (value) {
dropChannel = value;
}
});
};
}
])
.constant("$dragImageConfig", {
height: 20,
width: 200,
padding: 10,
font: 'bold 11px Arial',
fontColor: '#eee8d5',
backgroundColor: '#93a1a1',
xOffset: 0,
yOffset: 0
})
.service("$dragImage", [
'$dragImageConfig',
function (defaultConfig) {
var ELLIPSIS = '…';
function fitString(canvas, text, config) {
var width = canvas.measureText(text).width;
if (width < config.width) {
return text;
}
while (width + config.padding > config.width) {
text = text.substring(0, text.length - 1);
width = canvas.measureText(text + ELLIPSIS).width;
}
return text + ELLIPSIS;
};
this.generate = function (text, options) {
var config = angular.extend({}, defaultConfig, options || {});
var el = document.createElement('canvas');
el.height = config.height;
el.width = config.width;
var canvas = el.getContext('2d');
canvas.fillStyle = config.backgroundColor;
canvas.fillRect(0, 0, config.width, config.height);
canvas.font = config.font;
canvas.fillStyle = config.fontColor;
var title = fitString(canvas, text, config);
canvas.fillText(title, 4, config.padding + 4);
var image = new Image();
image.src = el.toDataURL();
return {
image: image,
xOffset: config.xOffset,
yOffset: config.yOffset
};
}
}
]);
}(angular));
URL Demo :
http://plnkr.co/edit/ldGXZbKgHn2YnGGXdYrm?p=preview
input type is not working properly in IE.
Please suggest me any solution or hack.
Thanks!!
Current best solution i have found:
ko.bindingHandlers.clickedIn = (function () {
function getBounds(element) {
var pos = element.offset();
return {
x: pos.left,
x2: pos.left + element.outerWidth(),
y: pos.top,
y2: pos.top + element.outerHeight()
};
}
function hitTest(o, l) {
function getOffset(o) {
for (var r = { l: o.offsetLeft, t: o.offsetTop, r: o.offsetWidth, b: o.offsetHeight };
o = o.offsetParent; r.l += o.offsetLeft, r.t += o.offsetTop);
return r.r += r.l, r.b += r.t, r;
}
for (var b, s, r = [], a = getOffset(o), j = isNaN(l.length), i = (j ? l = [l] : l).length; i;
b = getOffset(l[--i]), (a.l == b.l || (a.l > b.l ? a.l <= b.r : b.l <= a.r))
&& (a.t == b.t || (a.t > b.t ? a.t <= b.b : b.t <= a.b)) && (r[r.length] = l[i]));
return j ? !!r.length : r;
}
return {
init: function (element, valueAccessor) {
var target = valueAccessor();
$(document).click(function (e) {
if (element._clickedInElementShowing === false && target()) {
var $element = $(element);
var bounds = getBounds($element);
var possibleOverlays = $("[style*=z-index],[style*=absolute]").not(":hidden");
$.each(possibleOverlays, function () {
if (hitTest(element, this)) {
var b = getBounds($(this));
bounds.x = Math.min(bounds.x, b.x);
bounds.x2 = Math.max(bounds.x2, b.x2);
bounds.y = Math.min(bounds.y, b.y);
bounds.y2 = Math.max(bounds.y2, b.y2);
}
});
if (e.clientX < bounds.x || e.clientX > bounds.x2 ||
e.clientY < bounds.y || e.clientY > bounds.y2) {
target(false);
}
}
element._clickedInElementShowing = false;
});
$(element).click(function (e) {
e.stopPropagation();
});
},
update: function (element, valueAccessor) {
var showing = ko.utils.unwrapObservable(valueAccessor());
if (showing) {
element._clickedInElementShowing = true;
}
}
};
})();
It works by first query for all elements with either z-index or absolute position that are visible. It then hit tests those elemnts against the elemnet I want to hide if click outside. If its a hit I calculate a new bound retacle which takes into acount the overlay bounds.
Its not rock solid, but works. Please feel free to comment if you see problems with above approuch
Old question
I'm using Knockout but this applies to DOM/Javascript in general
Im trying to find a reliable way if detecting of you click outside of a element. My code looks like this
ko.bindingHandlers.clickedIn = {
init: function (element, valueAccessor) {
var target = valueAccessor();
var clickedIn = false;
ko.utils.registerEventHandler(document, "click", function (e) {
if (!clickedIn && element._clickedInElementShowing === false) {
target(e.target == element);
}
clickedIn = false;
element._clickedInElementShowing = false;
});
ko.utils.registerEventHandler(element, "click", function (e) {
clickedIn = true;
});
},
update: function (element, valueAccessor) {
var showing = ko.utils.unwrapObservable(valueAccessor());
if (showing) {
element._clickedInElementShowing = true;
}
}
};
It works by both listening to click on target element and document. If you click on document but not target element you click outside of it. This works, but, not for overlay items like datepickers etc. This is because these are not inside the target element but in the body. Can I fix this? Are there better way of determine if clicking outside of element?
edit: This kind of works, but only if the overlay is smaller than the element i want to monitor
ko.bindingHandlers.clickedIn = {
init: function (element, valueAccessor) {
var target = valueAccessor();
$(document).click(function (e) {
if (element._clickedInElementShowing === false) {
var $element = $(element);
var pos = $element.offset();
if (e.clientX < pos.left || e.clientX > (pos.left + $element.width()) ||
e.clientY < pos.top || e.clientY > (pos.top + $element.height())) {
target(false);
}
}
element._clickedInElementShowing = false;
});
$(element).click(function (e) {
e.stopPropagation();
});
},
update: function (element, valueAccessor) {
var showing = ko.utils.unwrapObservable(valueAccessor());
if (showing) {
element._clickedInElementShowing = true;
}
}
};
I would like a more rock solid approuch
This is how I usually solve it:
http://jsfiddle.net/jonigiuro/KLxnV/
$('.container').on('click', function(e) {
alert('hide the child');
});
$('.child').on('click', function(e) {
alert('do nothing');
e.stopPropagation(); //THIS IS THE IMPORTANT PART
});
I don't know how your overlay items are generated, but you could always check if the click target is a child of the element you want to constrain your clicks to.