I have AngularJS directive like this:
(function () {
'use strict';
// better click that ingore drag
angular
.module('module')
.directive('exClick', exClick);
exClick.$inject = [];
function exClick() {
return {
restrict: 'A',
scope: {
exClick: '&'
},
link: function ($scope, $element) {
var isDragging = false;
function mousemove() {
isDragging = true;
$(window).off('mousemove', mousemove);
}
var timer;
$element.mousedown(function() {
isDragging = false;
// there is wierd issue where move is triggerd just
// after mousedown even without moving the cursor
timer = setTimeout(function() {
$(window).mousemove(mousemove);
}, 100);
}).mouseup(function(e) {
var wasDragging = isDragging;
isDragging = false;
clearTimeout(timer);
$(window).off('mousemove', mousemove);
if (!wasDragging) {
$scope.$apply($scope.exClick);
}
});
$scope.$on('$destroy', function() {
$(window).off('mousemove', mousemove);
$element.off('mousedown mouseup');
});
}
}
}
})();
and I want to use like normal ng event I have ng-click on table row and on row controls I have ng-click="$event.stopPropagation()". I've replaced row with ex-click and I want to use ex-click="$event.stopPropagation()". I can use ng-mouseup to prevent the event from happening, but I want to know how to make my custom event to behave the same as native ng event.
I've tried:
$scope.exClick({$event: e});
and
$scope.$event = e;
$scope.$apply();
$scope.exClick();
Found in the source code ngClick is kind of normal AngularJS directive (it's added differently but it have the same compile and link, so it should work the same).
And for reference here is the copy of the code:
forEach(
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(eventName) {
var directiveName = directiveNormalize('ng-' + eventName);
ngEventDirectives[directiveName] = ['$parse', '$rootScope', '$exceptionHandler', function($parse, $rootScope, $exceptionHandler) {
return createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsyncEvents[eventName]);
}];
}
);
function createEventDirective($parse, $rootScope, $exceptionHandler, directiveName, eventName, forceAsync) {
return {
restrict: 'A',
compile: function($element, attr) {
// NOTE:
// We expose the powerful `$event` object on the scope that provides access to the Window,
// etc. This is OK, because expressions are not sandboxed any more (and the expression
// sandbox was never meant to be a security feature anyway).
var fn = $parse(attr[directiveName]);
return function ngEventHandler(scope, element) {
element.on(eventName, function(event) {
var callback = function() {
fn(scope, {$event: event});
};
if (!$rootScope.$$phase) {
scope.$apply(callback);
} else if (forceAsync) {
scope.$evalAsync(callback);
} else {
try {
callback();
} catch (error) {
$exceptionHandler(error);
}
}
});
};
}
};
}
Related
I'm just working on a projet in Angular 1. The task is to do a DOM tree from json object and manipulate with it through native drag'n'drop DOM operations (with out jQuery and etc). I've been done parsing json-> dom function, loading json from server, some drag'n'drop event handlers. Now my question is how to update a json object (from this object I get a ul>li tree structure) when I done a DROP event.
Now, code
View
<div ng-controller="TreeController">
<ng-dom-tree
style="background-color: #000"
ng-model = "treeJson"
class="tree"
ng-draggable
ng-droppable>
</ng-dom-tree>
</div>
Controller
.controller('TreeController', TreeController);
TreeController.$inject = ['treeService', '$scope'];
function TreeController(treeService, $scope) {
$scope.treeJson = '';
treeService.getTree().success(function (data) {
$scope.treeJson = data;
});
}
Main directive
.directive('ngDomTree', ngDomTree)
ngDomTree.$inject = [];
function ngDomTree() {
var isEmpty = function (object) {
for (var key in object) {
return false;
}
return true;
};
function createTree(tree, list) { /*creating tree -> json to dom*/}
return {
restrict: 'E',
replace: true,
link: function (scope, elt, attrs) {
scope.$watch('treeJson', function (data) {
if (isEmpty(data))
return;
**CAN'T HANDLE DATA CHANGING HERE**
elt.append(document.createElement('ul'));
createTree(data, document.getElementsByTagName('ul')[0]);
});
}
}
}
Sup directive
.directive('ngDroppable', ngDroppable)
ngDroppable.$inject = [];
function ngDroppable() {
var parseTreeToJson = function(tree){/* dom to json */}
return {
restrict: 'A',
link: function (scope, elt, attrs) {
elt.on('mouseover', function (e) {
var droppableElt = e.target || event.target;
if (!droppableElt.classList.contains('tree__node') && !droppableElt.classList.contains('tree__branch'))
return;
droppableElt.addEventListener(
'dragover',
function (e) {
this.classList.add('navigator');
e.dataTransfer.dropEffect = 'move';
if (e.preventDefault)
e.preventDefault();
this.classList.add('over');
return false;
},
false
);
droppableElt.addEventListener(
'dragenter',
function (e) {
this.classList.add('over');
return false;
},
false
);
droppableElt.addEventListener(
'dragleave',
function (e) {
this.classList.remove('over');
this.classList.remove('navigator');
return false;
},
false
);
droppableElt.addEventListener(
'drop',
function (e) {
if (e.stopPropagation) e.stopPropagation();
this.classList.remove('over');
let item = document.getElementById(e.dataTransfer.getData('Text'));
this.appendChild(item);
item.id = '';
//updating here
scope.treeJson = parseTreeToJson(elt[0].children[0]);
return false;
},
false
);
});
}
}
}
So, In Sup directive when drop event created, and I'm reinit treeJson variable, after that I need in main directive reinitializing the tree and also in controller get new json structure from this variable, because $watch is used, but it isn't happened.
PLEASE HELP
THNKS FOR ATTENTION :)
P.S. Here it is in Plnkr.co
Since you are using native DOM, it bypasses angular's processors. You need to call scope.$digest() after changing angular's state to tell it that something changed.
droppableElt.addEventListener(
'drop',
function (e) {
if (e.stopPropagation) e.stopPropagation();
this.classList.remove('over');
let item = document.getElementById(e.dataTransfer.getData('Text'));
this.appendChild(item);
item.id = '';
scope.treeJson = parseTreeToDOM(elt[0].children[0]);
scope.$digest();
},
false
);
I have an Angular state which can rotate between three templates each controlled by its own directive. The directives have event listeners, but as I rotate through the directives the event listeners add up by one and after the first transition to another directive everything starts to get buggy. I have attempted to fix these bugs but to no avail. Here is an example of one of the three directives:
angular.module('app').directive('presetStationOne', function($interval, $parse, $compile, PresetFactory){
var linker = function(scope, element, attrs){
PresetFactory.assign(1,6,scope);
scope.$watch('page1', function(newVal, oldVal){
if(newVal === true) {
allEncompassing(document, PresetFactory, scope, "start");
}
});
scope.$on('$destroy', function(){
allEncompassing(document, PresetFactory, scope, "finish");
});
};
return {
restrict: 'EA',
scope: false,
link: linker,
templateUrl: 'public/templates/freeplay/presetspage1.html'
};
});
And here is the function allEncompassing(), which is all three presetStation directives. They all use PresetFactory and when I change from one directive to another, the calls on PresetFactory incrementally increase.
function allEncompassing(document, PresetFactory, scope, msg){
if (msg === "finish"){
removeMyListeners();
}
function clickListen(e){
var f;
if (!e.target.attributes.stationnumber){
if (undefined){
return;
} else if(!e.target.parentNode || !e.target.parentNode.parentNode || !e.target){
return;
} else if (!e.target.parentNode.parentNode.attributes.hasOwnProperty('stationnumber')){
return;
} else {
return f = e.target.parentNode.parentNode.attributes;
}
} else {
return f = e.target.attributes;
}
}
function resetMouseup(PresetFactory){
restartMyListeners();
PresetFactory.mouseUp();
}
function execMouseup(e){
resetMouseup(PresetFactory);
}
function execClickListen(e){
var f = clickListen(e);
if (f !== undefined){
PresetFactory.mouseDown(f, scope);
scope.$digest();
restartMyListeners();
} else {
return;
}
}
function restartMyListeners(){
restartMousedown();
document.removeEventListener('mouseup', execMouseup);
document.addEventListener('mouseup', execMouseup);
}
function restartMousedown(){
document.removeEventListener('mousedown', execClickListen);
document.addEventListener('mousedown', execClickListen);
}
function removeMyListeners(){
document.removeEventListener('mousedown', execClickListen);
document.removeEventListener('mouseup', execMouseup);
}
if (msg === "start"){
restartMyListeners();
}
}
What is the best way to mitigate increasing these event listeners and keep it to a single event listener?
Here is the answer, and it's a lot easier than using event listeners. I thought I needed event listeners since I was watching a mousedown event to determine if I should set a radio station or change to a radio station. Instead, I used the $interval service and HTML attributes data-ng-mousedown="radioStn(1)" data-ng-mouseup="checkResult()":
var dial = null;
var promise = null;
var time = 0;
var check = false;
function defaultVal(){
dial = null;
promise = null;
time = 0;
}
function checkTime(dial, scope, $interval, $rootScope, PresetFactory){
$interval.cancel(promise);
if (check === true){
console.log(dial + ' check is true');
PresetFactory.setPreset(dial, scope);
} else {
console.log(dial + ' check is false');
PresetFactory.changeStn(dial);
}
defaultVal();
}
angular.module('app').directive('presetStationOne', function($rootScope, $interval, $parse, $compile, PresetFactory){
var linker = function(scope, element, attrs){
PresetFactory.assign(1,6,scope);
scope.radioStn = function(x){
dial = x;
promise = $interval(function(){
time += 100;
console.log(time);
if (time >= 1000){
check = true;
checkTime(dial, scope, $interval, $rootScope, PresetFactory);
return;
}
}, 100);
};
scope.checkResult = function(){
if (check === true){
check = false;
return;
} else {
checkTime(dial, scope, $interval, $rootScope, PresetFactory);
}
};
scope.$on('$destroy', function(){
defaultVal();
check = false;
});
};
return {
restrict: 'EA',
scope: false,
link: linker,
templateUrl: 'public/templates/freeplay/presetspage1.html'
};
});
In order to get this ability i have extended tooltip provider.
function customTooltip($document, $tooltip) {
var tooltip = $tooltip('customTooltip', 'customTooltip', 'click'),
parentCompile = angular.copy(tooltip.compile);
tooltip.compile = function (element, attrs) {
var parentLink = parentCompile(element, attrs);
return function postLink(scope, element, attrs) {
var firstTime = true;
parentLink(scope, element, attrs);
var onDocumentClick = function () {
if (firstTime) {
firstTime = false;
} else {
element.triggerHandler('documentClick');
}
};
var bindDocumentClick = function () {
$document.on('click', onDocumentClick);
};
var unbindDocumentClick = function () {
$document.off('click', onDocumentClick);
};
scope.$watch('tt_isOpen', function (newValue) {
firstTime = true;
if (newValue) {
bindDocumentClick();
} else {
unbindDocumentClick();
}
});
scope.$on('$destroy', function onTooltipDestroy() {
unbindDocumentClick();
});
};
};
return tooltip;
}
But this approach doesn't work already because there is no tt_isOpen property in scope now. Actually i can't see any of tooltip properties just only my parent scope. I guess this happend because of changes in tooltip.js 124 line https://github.com/angular-ui/bootstrap/blob/master/src/tooltip/tooltip.js#L124. Is there any way now to close tooltip by clicking outside it or at least to get isOpen flag?
There is a pull request that implements an outsideClick trigger for tooltips and popovers. It will be included in angular-ui 1.0.0, which is expected to be released by the end of the year. Once it is implemented, you will be able to simply add tooltip-trigger="outsideClick" to your element.
There is an open pull request Here to add this feature. A hack workaround you can try is to disable then enable the trigger element as the directive will call this method:
attrs.$observe( 'disabled', function ( val ) {
if (val && ttScope.isOpen ) {
hide();
}
});
This variant works on angular 1.3.15 and angular-ui version 0.13
function customTooltip($document, $tooltip) {
var tooltip = $tooltip('customTooltip', 'customTooltip', 'click'),
parentCompile = angular.copy(tooltip.compile);
tooltip.compile = function (element, attrs) {
var parentLink = parentCompile(element, attrs);
return function postLink(scope, element, attrs) {
parentLink(scope, element, attrs);
var isOpened = false;
element.bind('click', function () {
bindDocumentClick();
});
var onDocumentClick = function () {
if (!isOpened) {
isOpened = true;
} else {
element.triggerHandler('documentClick');
unbindDocumentClick();
isOpened = false;
}
};
var bindDocumentClick = function () {
$document.on('click', onDocumentClick);
};
var unbindDocumentClick = function () {
$document.off('click', onDocumentClick);
};
scope.$on('$destroy', function onTooltipDestroy() {
unbindDocumentClick();
});
};
};
return tooltip;
}
I'm trying to create drag and drop directives for angularJS by following this post: http://jasonturim.wordpress.com/2013/09/01/angularjs-drag-and-drop/
But I can not send any data because inside the dragstart event handler dataTransfer and the originalEvent are always null.
My drag directive looks like this:
directives.directive('draggSrc', ['$rootScope', 'UuidService', function ($rootScope, UuidService) {
var directive = {};
directive.restrict = 'A';
directive.link = function (scope, el, attrs) {
el.attr("draggable", "true");
var id = attrs.id;
if (!attrs.id) {
id = UuidService.new();
el.attr("id", id);
}
el.bind("dragstart", function (evt) {
console.log(evt);
console.log(evt.dataTransfer);
console.log(evt.originalEvent);
evt.dataTransfer.setData('text', id);
$rootScope.$emit("DRAG-START");
});
el.bind("dragend", function (evt) {
$rootScope.$emit("DRAG-END");
});
};
return directive;
}]);
I have also called $.event.props.push('dataTransfer').
In order to solve my problem I have switched from using jQuery's bind to using JavaScript's addEventListener and everything worked as expected:
directives.directive('draggSrc', ['$rootScope', 'UuidService', function ($rootScope, UuidService) {
var directive = {};
directive.restrict = 'A';
directive.link = function (scope, el, attrs) {
el.attr("draggable", "true");
var id = attrs.id;
if (!attrs.id) {
id = UuidService.new();
el.attr("id", id);
}
el.get(0).addEventListener("dragstart", function (evt) {
evt.dataTransfer.setData('text', id);
$rootScope.$emit("DRAG-START");
});
el.get(0).addEventListener("dragend", function (evt) {
$rootScope.$emit("DRAG-END");
});
};
return directive;
}]);
Had this same issue with a different fix: if you have an alert() or confirm() dialog in your drop handler, that clears out the event.dataTransfer.
I've created an application which enables the user to double click on an item to edit. I'd like to allow the same functionality on mobile devices, meaning the user would double tap to edit the item.
What is the simplest way to implement this? I'd rather not use any additional library (I have heard of Hammer and AngularTouch but haven't used neither before) nor jQuery (in my app I completely forgone jQuery).
If there the only way to implement this is using a library, what would be lightest and easiest?
Many thanks
EDIT: Adding code
This is my controller for editing the item:
// Double click to edit products
$scope.editItem = function (item) {
item.editing = true;
};
$scope.doneEditing = function (item) {
item.editing = false;
$http.put('/api/users?id' + $scope.User.id, $scope.User);
};
$scope.cancelEditing = function (item) {
item.editing = false;
};
$scope.deleteItem = function (item) {
delete $scope.User.todos[item.id];
$http.put('/api/users?id' + $scope.User.id, $scope.User);
};
And this is the my template (Jade)
p(ng-dblclick="editItem(todo)", ng-hide="todo.editing").todo-title
span {{todo.content}}
form(ng-submit="doneEditing(todo)" ng-show="todo.editing", class="inline-editing-2")
input(type="text", class="form-control", ng-model="todo.content")
div.btn-block
button(class="btn btn-success mr-1", ng-show="todo.editing", ng-click="cancelEditing(todo)")
span(ng-click="doneEditing(todo)").fa.fa-check-circle
button(class="btn btn-warning mr-1", ng-show="todo.editing", ng-click="cancelEditing(todo)")
span(ng-click="cancelEditing(todo)").fa.fa-times-circle
So as you can see I use ng-doubleclick to fire my function. I'd need something like ng-double-tab to fire up the double tap. I've been reading more about Hammer and will use Angular Hammer for double tap but I'm not sure how it works...
You can use ios-dblclick, a directive I wrote to handle double click event on mobile browser (write it for iOS, but works on other browsers). It has no dependency and works like ng-dblclick.
It's available here on Github.
Here's an example
<div ios-dblclick="removePhoto()"></div>
Here is the code of this directive
app.directive('iosDblclick',
function () {
const DblClickInterval = 300; //milliseconds
var firstClickTime;
var waitingSecondClick = false;
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.bind('click', function (e) {
if (!waitingSecondClick) {
firstClickTime = (new Date()).getTime();
waitingSecondClick = true;
setTimeout(function () {
waitingSecondClick = false;
}, DblClickInterval);
}
else {
waitingSecondClick = false;
var time = (new Date()).getTime();
if (time - firstClickTime < DblClickInterval) {
scope.$apply(attrs.iosDblclick);
}
}
});
}
};
});
You could always implement your own double tap directive. Start by looking at touchstart and touchend . Bind to these events, and check for multiple taps within some designated period of time.
As far as libraries, we've used this to handle doubletaps for mobile devices in angular
https://github.com/technoweenie/jquery.doubletap
The solution above is not working on my IOS - But I found an other Solution, which is working fine on my IPhone:
Just sharing for you:
http://jsfiddle.net/9Ymvt/3397/
fessmodule.directive('onDoubleClick', function($timeout) {
return {
restrict: 'A',
link: function($scope, $elm, $attrs) {
var clicks=0;
$elm.bind('click', function(evt) {
console.log('clicked');
clicks++;
if (clicks == 1) {
$timeout(function(){
if(clicks == 1) {
//single_click_callback.call(self, event);
} else {
$scope.$apply(function() {
console.log('clicked');
$scope.$eval($attrs.onDoubleClick)
});
}
clicks = 0;
}, 300);}
});
}
};
});