Accessing hammer event info from Angular directive inside controller - angularjs

I'm new to Angular, and I'm trying to get the XY coordinates of a tap using angular-hammer.js directives. Here's how the directives are set up:
var hmTouchevents = angular.module('hmTouchevents', []),
hmGestures = ['hmHold:hold',
'hmTap:tap',
'hmDoubletap:doubletap',
'hmDrag:drag',
'hmDragup:dragup',
'hmDragdown:dragdown',
'hmDragleft:dragleft',
'hmDragright:dragright',
'hmSwipe:swipe',
'hmSwipeup:swipeup',
'hmSwipedown:swipedown',
'hmSwipeleft:swipeleft',
'hmSwiperight:swiperight',
'hmTransform:transform',
'hmRotate:rotate',
'hmPinch:pinch',
'hmPinchin:pinchin',
'hmPinchout:pinchout',
'hmTouch:touch',
'hmRelease:release'];
angular.forEach(hmGestures, function(name){
var directive = name.split(':'),
directiveName = directive[0],
eventName = directive[1];
hmTouchevents.directive(directiveName, ["$parse", function($parse) {
return {
scope: true,
link: function(scope, element, attr) {
var fn, opts;
fn = $parse(attr[directiveName]);
opts = $parse(attr["hmOptions"])(scope, {});
scope.hammer = scope.hammer || Hammer(element[0], opts);
return scope.hammer.on(eventName, function(event) {
return scope.$apply(function() {
return fn(scope, {
$event: event
});
});
});
}
};
}
]);
});
My html looks like this:
<div ng-controller="IndexCtrl" >
<div class='tap-area' hm-tap="tap();">
</div>
</div>
My controller looks like this:
App.controller('IndexCtrl', function ($scope, Myapp) {
$scope.tap = function(ev){
//How do I get the event.gesture.center.pageX in here?
};
});

I figured out how to make this work. After return scope.hammer.on(eventName, function(event) { I added scope.event = event; and then in my controller I can get XY coords of a tap by using this.event.center.pageX or this.event.center.pageY.

It was posted long time ago but here is another solution.
Just add $event to your html

Related

AngularJS - view not updated when directive changes data in service

I'm using a directive to interact with a service and am encountering some trouble with the view showing the latest data.
I setup the following example. You can see that when a controller interacts with the Service, the view will update with the latest data. If you click the directive link, you can see in the console the data was changed but the view is not updated with that data.
http://jsfiddle.net/kisonay/pv8towqc/
What am I missing?
JavaScript:
var app = angular.module('myApp', []);
app.factory('Service', function() {
var Service = {};
Service.data = {};
Service.data.active = false;
Service.data.one = {};
Service.data.many = [];
Service.changeActive = function(state) {
state = state || false;
Service.data.active = state;
};
Service.setOne = function(one) {
Service.data.one = one;
};
Service.setMany = function(many) {
Service.data.many = many;
};
return Service;
});
app.directive('launcher', function(Service) {
return {
restrict: "A",
link: function(scope, elem, attrs) {
elem.bind('click', function(event) {
if (Service.data.active) {
Service.changeActive(false);
} else {
Service.changeActive(true);
}
console.log(Service.data); // shows data changed
});
}
};
});
function Ctrl1($scope, Service) {
$scope.ServiceData = Service.data;
}
function Ctrl2($scope, Service) {
$scope.active = function() {
Service.changeActive(true);
};
$scope.inactive = function() {
Service.changeActive(false);
};
}
HTML
<div ng-controller="Ctrl1">
{{ServiceData}}
</div>
<hr />
<div ng-controller="Ctrl2">
Directive: <a href="#" launcher>Change</a>
<hr /> Controller:
<button ng-click="active()">
Active
</button>
<button ng-click="inactive()">
Inactive
</button>
</div>
Your event listener executes but Angular doesn't know anything about it, so it doesn't know it has to detect changes.
Add scope.$apply(); at the evn of the click listener, and it will work as expected.
app.directive('launcher', function(Service) {
return {
restrict: "A",
link: function(scope, elem, attrs) {
elem.bind('click', function(event) {
scope.$apply(function() {
if (Service.data.active) {
Service.changeActive(false);
} else {
Service.changeActive(true);
}
});
});
}
};
});

How to call function in directive from button click

How do I call a function in a directive from a button click? I have been trying and have come up with this (but it is not working):
HTML
<div ng-controller="myMapCTRL as myMapctrl">
<div id="panel">
<input ng-click="updateMap()" type=button value="Remove Path">
</div>
<my-map-with-path id="map-canvas" class="map-canvas" ng-if="dataHasLoaded" ></my-map-with-path>
</div>
Controller
app.controller('myMapCTRL', ['$scope', 'PathService', function($scope, PathService){
//console.log('in controller');
$scope.removed = false;
if(typeof $scope.paths ==='undefined') {
$scope.dataHasLoaded = false;
$scope.center = new google.maps.LatLng(51.5130300, -0.3202410);
PathService.getPaths().then(function(data){
$scope.paths = data;
$scope.dataHasLoaded = true;
//console.log('paths loaded');
});
};
}]);
Directive
app.directive('myMapWithPath', [function() {
return{
restrict: 'AE',
template: '<div></div>',
replace: true,
controller: 'myMapCTRL',
link: function(scope, element, attrs){
//console.log('in link');
scope.updateMap = function() {
console.log('inside updateMap()');
}
var map, path = new google.maps.MVCArray(),
service = new google.maps.DirectionsService(), poly;
//var center = new google.maps.LatLng(51.5130300, -0.3202410);
var myOptions = {
zoom: 15,
center: scope.center,
mapTypeId: google.maps.MapTypeId.ROADMAP,
mapTypeControlOptions: {
mapTypeIds: [google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.HYBRID,
google.maps.MapTypeId.SATELLITE]
},
disableDoubleClickZoom: true,
scrollwheel: false,
draggableCursor: "crosshair"
}
map = new google.maps.Map(document.getElementById("map-canvas"), myOptions);
poly = new google.maps.Polyline({ map: map });
for(var i = 0; i < scope.paths['j'].length; i++) {
var lat = scope.paths['j'][i]['k']
var lng = scope.paths['j'][i]['D']
var lat_lng = new google.maps.LatLng(lat, lng);
path.push(lat_lng);
}
poly.setPath(path);
google.maps.event.addListener(map, "click", function(evt) {
if (path.getLength() === 0) {
path.push(evt.latLng);
poly.setPath(path);
} else {
service.route({
origin: path.getAt(path.getLength() - 1),
destination: evt.latLng,
travelMode: google.maps.DirectionsTravelMode.DRIVING
}, function(result, status) {
if (status == google.maps.DirectionsStatus.OK) {
for (var i = 0, len = result.routes[0].overview_path.length;
i < len; i++) {
path.push(result.routes[0].overview_path[i]);
}
}
});
}
//console.log(path);
});
}
}
}]);
I want to call scope.updateMap from the button click but it is not firing in the console.
This won't work because the ng-click is outside the directive.
You should move the function updateMap to the $scope of myMapCTRL
Having dug around a little more, it seems quite normal to use a shared service to communicate between a controller and a directive.
The general idea is this:
HTML
<div ng-controller="myMapCTRL as myMapctrl">
<div id="panel">
<input ng-click="updateMap()" type=button value="Remove Path">
</div>
<my-map-with-path id="map-canvas" class="map-canvas" ng-if="dataHasLoaded" ></my-map-with-path>
</div>
SharedService
app.factory('mySharedService', function($rootScope) {
var sharedService = {};
sharedService.doSomething = function() {
$rootScope.$broadcast('messageBroadcast');
};
return sharedService;
});
Controller
app.controller('myMapCTRL', ['$scope', 'mySharedService',
function($scope, sharedService){
$scope.updateMap = function() {
sharedService.doSomething();
}
}]);
Directive
app.directive('myMapWithPath', [function() {
return{
restrict: 'AE',
template: '<div></div>',
replace: true,
controller: 'myMapCTRL',
link: function(scope, element, attrs){
scope.$on('messageBroadcast', function() {
console.log('in directive broadcast message');
});
...
}
}
}]);
The idea seems to be that the controller calls a function in the shared service which "broadcasts" a message out. The directive waits for that message and when it is received, it does something amazing.
I am not sure if I need to inject the shared service into the directive or link function but it seems to work without it.

How do I make angular.js reevaluate / recompile inner html?

I'm making a directive that modifies it's inner html. Code so far:
.directive('autotranslate', function($interpolate) {
return function(scope, element, attr) {
var html = element.html();
debugger;
html = html.replace(/\[\[(\w+)\]\]/g, function(_, text) {
return '<span translate="' + text + '"></span>';
});
element.html(html);
}
})
It works, except that the inner html is not evaluated by angular. I want to trigger a revaluation of element's subtree. Is there a way to do that?
Thanks :)
You have to $compile your inner html like
.directive('autotranslate', function($interpolate, $compile) {
return function(scope, element, attr) {
var html = element.html();
debugger;
html = html.replace(/\[\[(\w+)\]\]/g, function(_, text) {
return '<span translate="' + text + '"></span>';
});
element.html(html);
$compile(element.contents())(scope); //<---- recompilation
}
})
Here's a more generic method I developed to solve this problem:
angular.module('kcd.directives').directive('kcdRecompile', function($compile, $parse) {
'use strict';
return {
scope: true, // required to be able to clear watchers safely
compile: function(el) {
var template = getElementAsHtml(el);
return function link(scope, $el, attrs) {
var stopWatching = scope.$parent.$watch(attrs.kcdRecompile, function(_new, _old) {
var useBoolean = attrs.hasOwnProperty('useBoolean');
if ((useBoolean && (!_new || _new === 'false')) || (!useBoolean && (!_new || _new === _old))) {
return;
}
// reset kcdRecompile to false if we're using a boolean
if (useBoolean) {
$parse(attrs.kcdRecompile).assign(scope.$parent, false);
}
// recompile
var newEl = $compile(template)(scope.$parent);
$el.replaceWith(newEl);
// Destroy old scope, reassign new scope.
stopWatching();
scope.$destroy();
});
};
}
};
function getElementAsHtml(el) {
return angular.element('<a></a>').append(el.clone()).html();
}
});
You use it like so:
HTML
<div kcd-recompile="recompile.things" use-boolean>
<div ng-repeat="thing in ::things">
<img ng-src="{{::thing.getImage()}}">
<span>{{::thing.name}}</span>
</div>
</div>
JavaScript
$scope.recompile = { things: false };
$scope.$on('things.changed', function() { // or some other notification mechanism that you need to recompile...
$scope.recompile.things = true;
});
Edit
If you're looking at this, I would seriously recommend looking at the website's version as that is likely to be more up to date.
This turned out to work even better than #Reza's solution
.directive('autotranslate', function() {
return {
compile: function(element, attrs) {
var html = element.html();
html = html.replace(/\[\[(\w+)\]\]/g, function(_, text) {
return '<span translate="' + text + '"></span>';
});
element.html(html);
}
};
})
Reza's code work when scope is the scope for all of it child elements. However, if there's an ng-controller or something in one of the childnodes of this directive, the scope variables aren't found. However, with this solution ^, it just works!

AngularJS - bind to directive resize

How can i be notified when a directive is resized?
i have tried
element[0].onresize = function() {
console.log(element[0].offsetWidth + " " + element[0].offsetHeight);
}
but its not calling the function
(function() {
'use strict';
// Define the directive on the module.
// Inject the dependencies.
// Point to the directive definition function.
angular.module('app').directive('nvLayout', ['$window', '$compile', layoutDirective]);
function layoutDirective($window, $compile) {
// Usage:
//
// Creates:
//
var directive = {
link: link,
restrict: 'EA',
scope: {
layoutEntries: "=",
selected: "&onSelected"
},
template: "<div></div>",
controller: controller
};
return directive;
function link(scope, element, attrs) {
var elementCol = [];
var onSelectedHandler = scope.selected();
element.on("resize", function () {
console.log("resized.");
});
$(window).on("resize",scope.sizeNotifier);
scope.$on("$destroy", function () {
$(window).off("resize", $scope.sizeNotifier);
});
scope.sizeNotifier = function() {
alert("windows is being resized...");
};
scope.onselected = function(id) {
onSelectedHandler(id);
};
scope.$watch(function () {
return scope.layoutEntries.length;
},
function (value) {
//layout was changed
activateLayout(scope.layoutEntries);
});
function activateLayout(layoutEntries) {
for (var i = 0; i < layoutEntries.length; i++) {
if (elementCol[layoutEntries[i].id]) {
continue;
}
var div = "<nv-single-layout-entry id=slot" + layoutEntries[i].id + " on-selected='onselected' style=\"position:absolute;";
div = div + "top:" + layoutEntries[i].position.top + "%;";
div = div + "left:" + layoutEntries[i].position.left + "%;";
div = div + "height:" + layoutEntries[i].size.height + "%;";
div = div + "width:" + layoutEntries[i].size.width + "%;";
div = div + "\"></nv-single-layout-entry>";
var el = $compile(div)(scope);
element.append(el);
elementCol[layoutEntries[i].id] = 1;
}
};
}
function controller($scope, $element) {
}
}
})();
Use scope.$watch with a custom watch function:
scope.$watch(
function () {
return [element[0].offsetWidth, element[0].offsetHeight].join('x');
},
function (value) {
console.log('directive got resized:', value.split('x'));
}
)
You would typically want to watch the element's offsetWidth and offsetHeight properties. With more recent versions of AngularJS, you can use $scope.$watchGroup in your link function:
app.directive('myDirective', [function() {
function link($scope, element) {
var container = element[0];
$scope.$watchGroup([
function() { return container.offsetWidth; },
function() { return container.offsetHeight; }
], function(values) {
// Handle resize event ...
});
}
// Return directive definition ...
}]);
However, you may find that updates are quite slow when watching the element properties directly in this manner.
To make your directive more responsive, you could moderate the refresh rate by using $interval. Here's an example of a reusable service for watching element sizes at a configurable millisecond rate:
app.factory('sizeWatcher', ['$interval', function($interval) {
return function (element, rate) {
var self = this;
(self.update = function() { self.dimensions = [element.offsetWidth, element.offsetHeight]; })();
self.monitor = $interval(self.update, rate);
self.group = [function() { return self.dimensions[0]; }, function() { return self.dimensions[1]; }];
self.cancel = function() { $interval.cancel(self.monitor); };
};
}]);
A directive using such a service would look something like this:
app.directive('myDirective', ['sizeWatcher', function(sizeWatcher) {
function link($scope, element) {
var container = element[0],
watcher = new sizeWatcher(container, 200);
$scope.$watchGroup(watcher.group, function(values) {
// Handle resize event ...
});
$scope.$on('$destroy', watcher.cancel);
}
// Return directive definition ...
}]);
Note the call to watcher.cancel() in the $scope.$destroy event handler; this ensures that the $interval instance is destroyed when no longer required.
A JSFiddle example can be found here.
Here a sample code of what you need to do:
APP.directive('nvLayout', function ($window) {
return {
template: "<div></div>",
restrict: 'EA',
link: function postLink(scope, element, attrs) {
scope.onResizeFunction = function() {
scope.windowHeight = $window.innerHeight;
scope.windowWidth = $window.innerWidth;
console.log(scope.windowHeight+"-"+scope.windowWidth)
};
// Call to the function when the page is first loaded
scope.onResizeFunction();
angular.element($window).bind('resize', function() {
scope.onResizeFunction();
scope.$apply();
});
}
};
});
The only way you would be able to detect size/position changes on an element using $watch is if you constantly updated your scope using something like $interval or $timeout. While possible, it can become an expensive operation, and really slow your app down.
One way you could detect a change on an element is by calling
requestAnimationFrame.
var previousPosition = element[0].getBoundingClientRect();
onFrame();
function onFrame() {
var currentPosition = element[0].getBoundingClientRect();
if (!angular.equals(previousPosition, currentPosition)) {
resiszeNotifier();
}
previousPosition = currentPosition;
requestAnimationFrame(onFrame);
}
function resiszeNotifier() {
// Notify...
}
Here's a Plunk demonstrating this. As long as you're moving the box around, it will stay red.
http://plnkr.co/edit/qiMJaeipE9DgFsYd0sfr?p=preview
A slight variation on Eliel's answer worked for me. In the directive.js:
$scope.onResizeFunction = function() {
};
// Call to the function when the page is first loaded
$scope.onResizeFunction();
angular.element($(window)).bind('resize', function() {
$scope.onResizeFunction();
$scope.$apply();
});
I call
$(window).resize();
from within my app.js. The directive's d3 chart now resizes to fill the container.
Here is my take on this directive (using Webpack as bundler):
module.exports = (ngModule) ->
ngModule.directive 'onResize', ['Callback', (Callback) ->
restrict: 'A'
scope:
onResize: '#'
onResizeDebounce: '#'
link: (scope, element) ->
container = element[0]
eventName = scope.onResize || 'onResize'
delay = scope.onResizeDebounce || 1000
scope.$watchGroup [
-> container.offsetWidth ,
-> container.offsetHeight
], _.debounce (values) ->
Callback.event(eventName, values)
, delay
]

AngularJS Passing Variable to Directive

I'm new to angularjs and am writing my first directive. I've got half the way there but am struggling figuring out how to pass some variables to a directive.
My directive:
app.directive('chart', function () {
return{
restrict: 'E',
link: function (scope, elem, attrs) {
var chart = null;
var opts = {};
alert(scope[attrs.chartoptions]);
var data = scope[attrs.ngModel];
scope.$watch(attrs.ngModel, function (v) {
if (!chart) {
chart = $.plot(elem, v, opts);
elem.show();
} else {
chart.setData(v);
chart.setupGrid();
chart.draw();
}
});
}
};
});
My controller:
function AdListCtrl($scope, $http, $rootScope, $compile, $routeParams, AlertboxAPI) {
//grabing ad stats
$http.get("/ads/stats/").success(function (data) {
$scope.exports = data.ads;
if ($scope.exports > 0) {
$scope.show_export = true;
} else {
$scope.show_export = false;
}
//loop over the data
var chart_data = []
var chart_data_ticks = []
for (var i = 0; i < data.recent_ads.length; i++) {
chart_data.push([0, data.recent_ads[i].ads]);
chart_data_ticks.push(data.recent_ads[i].start);
}
//setup the chart
$scope.data = [{data: chart_data,lines: {show: true, fill: true}}];
$scope.chart_options = {xaxis: {ticks: [chart_data_ticks]}};
});
}
My Html:
<div class='row-fluid' ng-controller="AdListCtrl">
<div class='span12' style='height:400px;'>
<chart ng-model='data' style='width:400px;height:300px;display:none;' chartoptions="chart_options"></chart>
{[{ chart_options }]}
</div>
</div>
I can access the $scope.data in the directive, but I can't seem to access the $scope.chart_options data.. It's definelty being set as If I echo it, it displays on the page..
Any ideas what I'm doing wrong?
UPDATE:
For some reason, with this directive, if I move the alert(scope[attrs.chartoptions]); to inside the $watch, it first alerts as "undefined", then again as the proper value, otherwise it's always undefined. Could it be related to the jquery flot library I'm using to draw the chart?
Cheers,
Ben
One problem I see is here:
scope.$watch(attrs.ngModel, function (v) {
The docs on this method are unfortunately not that clear, but the first argument to $watch, the watchExpression, needs to be an angular expression string or a function. So in your case, I believe that you need to change it to:
scope.$watch("attrs.ngModel", function (v) {
If that doesn't work, just post a jsfiddle or jsbin.com with your example.

Resources