I have 2 elements that are bound to a click function inside a directive using Angular.
The problem I'm having is when one of those elements are are clicked very quickly, the other element would fire.
You can see an example here: http://430designs.com/xperience/black-label-app/deck.php
If you click the "X" several times, rapidly, you'll see the heart glow instead of the "X". You may have to do it a few times to actually see it happen, but it will happen.
I need to disable the heart/dislike buttons after the click and then reenable them when the function is finished.
Here's my controller code. The directive for the "fake swipe" starts on line 87:
angular.module('black-label', ['ngTouch', 'ngSwippy'])
.controller('MainController', function($scope, $timeout, $window) {
$scope.cardsCollection = [{thumbnail:'images/deck/thor_01.jpg',collection:'thoroughbred',},{thumbnail:'images/deck/thor_02.jpg',collection:'thoroughbred',},{thumbnail:'images/deck/thor_03.jpg',collection:'thoroughbred',},{thumbnail:'images/deck/thor_04.jpg',collection:'thoroughbred',},{thumbnail:'images/deck/thor_05.jpg',collection:'thoroughbred',},{thumbnail:'images/deck/thor_06.jpg',collection:'thoroughbred',},{thumbnail:'images/deck/rhap_01.jpg',collection:'rhapsody',},{thumbnail:'images/deck/rhap_02.jpg',collection:'rhapsody',},{thumbnail:'images/deck/rhap_03.jpg',collection:'rhapsody',},{thumbnail:'images/deck/rhap_04.jpg',collection:'rhapsody',},{thumbnail:'images/deck/rhap_05.jpg',collection:'rhapsody',},{thumbnail:'images/deck/rhap_06.jpg',collection:'rhapsody',},{thumbnail:'images/deck/cha_01.jpg',collection:'chalet',},{thumbnail:'images/deck/cha_02.jpg',collection:'chalet',},{thumbnail:'images/deck/cha_03.jpg',collection:'chalet',},{thumbnail:'images/deck/cha_04.jpg',collection:'chalet',},{thumbnail:'images/deck/cha_05.jpg',collection:'chalet',},{thumbnail:'images/deck/cha_06.jpg',collection:'chalet',},{thumbnail:'images/deck/mod_01.jpg',collection:'modern',},{thumbnail:'images/deck/mod_02.jpg',collection:'modern',},{thumbnail:'images/deck/mod_03.jpg',collection:'modern',},{thumbnail:'images/deck/mod_04.jpg',collection:'modern',},{thumbnail:'images/deck/mod_05.jpg',collection:'modern',},{thumbnail:'images/deck/mod_06.jpg',collection:'modern',},{thumbnail:'images/deck/ind_01.jpg',collection:'indulgence',},{thumbnail:'images/deck/ind_02.jpg',collection:'indulgence',},{thumbnail:'images/deck/ind_03.jpg',collection:'indulgence',},{thumbnail:'images/deck/ind_04.jpg',collection:'indulgence',},{thumbnail:'images/deck/ind_05.jpg',collection:'indulgence',},{thumbnail:'images/deck/ind_06.jpg',collection:'indulgence',},{thumbnail:'images/deck/cnt_01.jpg',collection:'center-stage',},{thumbnail:'images/deck/cnt_02.jpg',collection:'center-stage',},{thumbnail:'images/deck/cnt_03.jpg',collection:'center-stage',},{thumbnail:'images/deck/cnt_04.jpg',collection:'center-stage',},{thumbnail:'images/deck/cnt_05.jpg',collection:'center-stage',},{thumbnail:'images/deck/cnt_06.jpg',collection:'center-stage',},{thumbnail:'images/deck/vin_01.jpg',collection:'vineyard',},{thumbnail:'images/deck/vin_02.jpg',collection:'vineyard',},{thumbnail:'images/deck/vin_03.jpg',collection:'vineyard',},{thumbnail:'images/deck/vin_04.jpg',collection:'vineyard',},{thumbnail:'images/deck/vin_05.jpg',collection:'vineyard',},{thumbnail:'images/deck/vin_06.jpg',collection:'vineyard',}];
// Do the shuffle
var shuffleArray = function(array) {
var m = array.length,
t, i;
// While there remain elements to shuffle
while (m) {
// Pick a remaining element
i = Math.floor(Math.random() * m--);
// And swap it with the current element.
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
};
$scope.deck = shuffleArray($scope.cardsCollection);
$scope.myCustomFunction = function() {
$timeout(function() {
$scope.clickedTimes = $scope.clickedTimes + 1;
$scope.actions.unshift({ name: 'Click on item' });
});
}; //end myCustomFunction
$scope.count = 0;
$scope.showinfo = false;
$scope.clickedTimes = 0;
$scope.actions = [];
$scope.picks = [];
var counterRight = 0;
var counterLeft = 0;
var newVar = $scope;
$scope.swipeend = function() {
$scope.actions.unshift({ name: 'Collection Empty' });
$window.location.href = 'theme-default.php';
}; //endswipeend
$scope.swipeLeft = function(person) {
//Essentially do nothing
$scope.actions.unshift({ name: 'Left swipe' });
$('.circle.x').addClass('dislike');
$('.circle.x').removeClass('dislike');
$(this).each(function() {
return counterLeft++;
});
}; //end swipeLeft
$scope.swipeRight = function(person) {
$scope.actions.unshift({ name: 'Right swipe' });
// Count the number of right swipes
$(this).each(function() {
return counterRight++;
});
$scope.picks.push(person.collection);
// Checking the circles
$('.circle').each(function() {
if (!$(this).hasClass('checked')) {
$(this).addClass('checked');
return false;
}
});
if (counterRight === 4) {
// Calculate and store the frequency of each swipe
var frequency = $scope.picks.reduce(function(frequency, swipe) {
var sofar = frequency[swipe];
if (!sofar) {
frequency[swipe] = 1;
} else {
frequency[swipe] = frequency[swipe] + 1;
}
return frequency;
}, {});
Object.keys(frequency).forEach(function(element) {
console.log('Person ', element,': ', frequency[element]);
});
var max = Math.max.apply(null, Object.keys(frequency).map(function(k){ return frequency[k]; })); // most frequent
// find key for the most frequent value
var winner = Object.keys(frequency).find(function(element){return frequency[element] == max; });
//Underscore
// var winner = _.findKey(frequency, val => val === max);
$window.location.href = 'theme-' + winner + '.php';
} //end 4 swipes
}; //end swipeRight
})
.directive('ngSwippy', ['swipe', function(swipe) {
return {
restrict: 'E',
link: function (scope, element, attrs, controller) {
$(".fake-swipe").on("click", function(evt) {
var sign = $(this).hasClass("swippy-like")?1:-1;
var card = $("div.content-wrapper.swipable-card:last", element/*"div.ng-swippy"*/);
$(this).addClass('happy');
setTimeout(function() {
card.trigger("mousemove");
},300);
$(this).removeClass('happy');
card.trigger("mousedown");
card.animate({ left:sign*$("body").width() }, 350, function() {
card.trigger("mouseup");
});
evt.preventDefault();
return false;
});
}
};
}]);
Similar to #LodewijkBogaards solution, you can also have a variable in the controller, which is true while the click listener is running. To implement such a behavior you simply add a variable to the controller (e.g. var isRunning = false;). You then need to add a condition to the click listener function:
var isRunning = false;
function click() {
if(!isRunning) {
isRunning = true;
[...your code...]
isRunning = false;
}
}
This will also work on elements like a <div/> or an <a/>, whereas the solution of adding ng-disabled will only work on buttons.
The general principle is this: The first thing you do when you handle the click event is set some boolean to true, e.g. $scope.actionInProgress = true. On your button you should have the attribute ng-disabled="!actionInProgress". Then when you action completes you simply set $scope.actionInProgress = false.
People answering questions on StackOverflow generally don't like doing your work in your codebase. People like answering questions that are reduced to the heart of the problem. I am also one of those people and I am quite certain from the looks of your code you are quite capable of making such a change slight change yourself.
Good luck!
Position top is not consistant every time ,
If user scrolls down when dom is still loading position will be different.
angular.module('users').directive('appplyProperty', ['$window', '$timeout',
function ($window, $timeout) {
var $win = angular.element($window);
return {
restrict: 'A',
link: function (scope, element, attrs) {
var = offsetTop = element.offset().top;
$win.on('scroll', function (e) {
var checkTheTop = $window.scrollY - offsetTop;
if (checkTheTop > 0) {
// apply css property top = checkTheTop ;
} else {
//do something
}
});
});
}
}]);
How to make sure position is calculated only after DOM is loaded or condition that now nothing is happening in the dom calculate position ?
Use ready method to ensure that your angular DOM is ready.
angular.element(document).ready(function () {
$win.on('scroll', function (e) {
var checkTheTop = $window.scrollY - offsetTop;
if (checkTheTop > 0) {
// apply css property top = checkTheTop ;
} else {
//do something
}
});
});
I build an application with interactive SVG map with angular.js
Whole app controlled by controller, svg wrapped in a directive, and every <path> in svg is a partial directive too.
When user clicked on a wall, <path> id is memorized in a custom service and next when user select a color, I need to fill this wall with that color.
The problem is that I can't access directive within a controller. I also cant share a scope with a directive because i have multiple nested directives in my controller and want to access them by id.
Maybe there is a design mistake? how can I achieve this workflow?
There is my code for controller, service and directives
ColorPicker Controller
app.controller("ColorPicker", function($scope, $timeout, appConfig, ColorService) {
$scope.colors = [];
$scope.currentColorSet = 0;
$scope.svgTemplateUrl = appConfig.arSvg[$scope.$parent.roomType.id][$scope.$parent.roomStyle.id] + 'over.svg';
$scope.maxColorSet = Math.max.apply(Math, jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors.map(function(color) {
return color.color.group;
}));
$scope.sliderConfig = {
min: 0,
max: $scope.maxColorSet,
step: 1
}
$scope.emptyColors = ( ColorService.getColors().length === 0 );
$scope.currentProps = {};
$scope.applyColor = function(color) {
console.log($scope.currentProps);
//**here I need to set fill with selected color to selected directive**
}
$scope.prevColorSet = function(){
if($scope.currentColorSet > 0) {
$scope.currentColorSet--;
generateColorSet($scope.currentColorSet);
}
}
$scope.nextColorSet = function(){
if($scope.currentColorSet < $scope.maxColorSet) {
$scope.currentColorSet++;
generateColorSet($scope.currentColorSet);
}
}
generateColorSet = function(setIndex){
$scope.colors = [];
for(var i = 0; i < jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors.length; i++){
if(jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors[i].color.group == setIndex) {
$scope.colors.push(jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors[i].color)
}
}
}
generateColorSet($scope.currentColorSet);
$scope.$watch("currentColorSet", function(newValue, oldValue) {
generateColorSet($scope.currentColorSet);
});
});
Directive for whole SVG
app.directive('svgmap', ['$compile', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var svg = $(element).find('svg');
svg.removeAttr('xmlns:a');
svg.attr('width', '869px');
svg.attr('height', '489px');
element.append(svg);
var regions = element[0].querySelectorAll('path');
angular.forEach(regions, function (path, key) {
var regionElement = angular.element(path);
regionElement.attr("region", "");
$compile(regionElement)(scope);
});
},
}
}]);
Directive for region (wall)
app.directive('region', ['$compile','ColorService','$timeout', function ($compile, ColorService, $timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.properties = {
elementId : element.attr("id"),
stroke: '',
fill: '#fff'
};
scope.regionClick = function () {
scope.currentProps = scope.properties;
if(ColorService.selectedRegion != scope.properties.elementId) {
scope.properties.stroke = '#e5514e';
ColorService.selectedRegion = scope.properties.elementId;
} else {
scope.properties.stroke = '';
ColorService.selectedRegion = '';
}
console.log('click: ' + scope.properties.elementId + ' : ' + scope.properties.stroke);
};
scope.setStroke = function () {
scope.stroke = '#e5514e';
};
scope.removeStroke = function() {
if(ColorService.selectedRegion != scope.properties.elementId) {
scope.properties.stroke = '';
}
//console.log('enter: ' + scope.elementId + ' : ' + scope.stroke);
};
scope.removeColor = function() {
console.log('removed');
scope.properties.fill = '#fff';
ColorService.remove(scope.properties.elementId);
};
ColorService.getColors().forEach(function(item) {
if(item.id === scope.properties.elementId) {
scope.properties.fill = item.info.val;
}
});
element.attr("ng-click", "regionClick()");
element.attr("ng-attr-stroke", "{{properties.stroke}}");
element.attr("ng-attr-fill", "{{properties.fill}}");
element.attr("ng-mouseenter", "setStroke()");
element.attr("ng-mouseleave", "removeStroke()");
element.attr("ng-right-click", "removeColor()");
element.after('<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />');
element.removeAttr("region");
$compile(element)(scope);
}
}
}]);
I have a directive in a table element that allows horizontal scroll. I have a fixed header which left position I update by binding the scroll event to the element that holds the directive(the table):
The link function of the directive updates the header position on the screen and brings a div over the table to block pointer-events.
It is obvious that this function forces some style recalc so my question is:
In the angular.js world what would be the best aproach. Should I use $animate, should I be setting the timeout the way I do it, would I avoid extended frames by requesting an animation frame(how is that done for this example?). Which way is best for performance?
Here is my current directive:
app.directive('checkScroll', function() {
return {
// require: '^?listeditorController',
link: function(scope, elem, attrs) {
var timer = null;
var screenBlock= document.getElementsByClassName('blockEventsScreen')[0];
// var lastLeft = 0;
// requestAnimationFrame( scope.onScroll );
elem.bind('scroll', function() {
if(timer !== null) {
//clear previous timer that turned off the during scroll tweaks
clearTimeout(timer);
clearInterval(scope.interval);
scope.interval = undefined;
//This sets the left style attribute of the table header container to the same value as the table body
var el=document.getElementsByClassName('fixedHeader')[0];
var newLeft = elem[0].getElementsByClassName('superResponsive')[0].getBoundingClientRect().left;
screenBlock.style['transform'] = 'translateX(0px)';
screenBlock.style['msTransform'] = 'translateX(0px)';
screenBlock.style['MozTransform'] = 'translateX(0px)';
screenBlock.style['WebkitTransform'] = 'translateX(0px)';
screenBlock.style['OTransform'] = 'translateX(0px)';
// var diff= Math.abs(newLeft - lastLeft );
// if(diff>0){
el.style['transform'] = 'translateX('+newLeft+'px)';
el.style['msTransform'] = 'translateX('+newLeft+'px)';
el.style['MozTransform'] = 'translateX('+newLeft+'px)';
el.style['WebkitTransform'] = 'translateX('+newLeft+'px)';
el.style['OTransform'] = 'translateX('+newLeft+'px)';
// lastLeft=newLeft;
// scope.$apply(function(){
// $scope.headerLeft=newLeft+'px';
// });
// requestAnimationFrame( scope.onScroll );
// }
}
timer = setTimeout(function() {
//remove screen of table to enable pointer-events
screenBlock.style['transform'] = 'translateX(-10000px)';
screenBlock.style['msTransform'] = 'translateX(-10000px)';
screenBlock.style['MozTransform'] = 'translateX(-10000px)';
screenBlock.style['WebkitTransform'] = 'translateX(-10000px)';
screenBlock.style['OTransform'] = 'translateX(-10000px)';
//restart scaleheader after scrolling
scope.interval = setInterval( function(){scope.scaleHeader();}, 200);
}, 200);
});
}
}
});
When using ng-click on a div:
<div ng-click="doSomething()">bla bla</div>
ng-click fires even if the user only selects or drags the div. How do I prevent that, while still enabling text selection?
In requiring something similar, where the usual text selection behavior is required on an element which should otherwise respond to ngClick, I wrote the following directive, which may be of use:
.directive('selectableText', function($window, $timeout) {
var i = 0;
return {
restrict: 'A',
priority: 1,
compile: function (tElem, tAttrs) {
var fn = '$$clickOnNoSelect' + i++,
_ngClick = tAttrs.ngClick;
tAttrs.ngClick = fn + '($event)';
return function(scope) {
var lastAnchorOffset, lastFocusOffset, timer;
scope[fn] = function(event) {
var selection = $window.getSelection(),
anchorOffset = selection.anchorOffset,
focusOffset = selection.focusOffset;
if(focusOffset - anchorOffset !== 0) {
if(!(lastAnchorOffset === anchorOffset && lastFocusOffset === focusOffset)) {
lastAnchorOffset = anchorOffset;
lastFocusOffset = focusOffset;
if(timer) {
$timeout.cancel(timer);
timer = null;
}
return;
}
}
lastAnchorOffset = null;
lastFocusOffset = null;
// delay invoking click so as to watch for user double-clicking
// to select words
timer = $timeout(function() {
scope.$eval(_ngClick, {$event: event});
timer = null;
}, 250);
};
};
}
};
});
Plunker: http://plnkr.co/edit/kkfXfiLvGXqNYs3N6fTz?p=preview
I had to deal with this, too, and came up with something much simpler. Basically you store the x-position on mousedown and then compare new x-position on mouseup:
ng-mousedown="setCurrentPos($event)"
ng-mouseup="doStuff($event)"
Function setCurrentPos():
var setCurrentPos = function($event) {
$scope.currentPos = $event.offsetX;
}
Function doStuff():
var doStuff = function ($event) {
if ($event.offsetX == $scope.currentPos) {
// Only do stuff here, when mouse was NOT dragged
} else {
// Do other stuff, which includes mouse dragging
}
}