How to Capture Touch Events with Angular Directive - angularjs

I would like to be able to capture the fact that a user moved their finger through a set of DOM elements on a touch device. This example works fine on a desktop browser but does not fire all the expected events when viewed in mobile Safari.
Working Plunkr (demonstrates the issue on mobile safari):
http://plnkr.co/edit/J8sfuJ9o6DorMSFlK9v2
HTML:
<body ng-controller="MainCtrl">
<p>Hello {{name}}! This works from a desktop browser but not from mobile Safari. I would simply like to be able to drag my finger down, across all four letters, and have their events fire. I thought that touchMove would work in place of mouseMove when running this Plunkr on iOS, but it doesn't.</p> Current letter: {{currentLetter}}
<div swipe-over="swipeOver()">A</div>
<div swipe-over="swipeOver()">B</div>
<div swipe-over="swipeOver()">C</div>
<div swipe-over="swipeOver()">D</div>
</body>
Javascript:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $log) {
$scope.name = 'World';
$scope.currentLetter = "";
$scope.swipeOver = function() {
$log.info("In swipeOver");
};
});
app.directive('swipeOver', function($log) {
return {
restrict: "A",
scope: true,
link: function(scope, element, attrs) {
// For touch devices
element.bind("touchmove", function() {
scope.$apply(function(evt) {
$log.info("in touchmove - " + element.text());
scope.$parent.currentLetter = element.text();
});
});
// For desktops
element.bind("mousemove", function(evt, e2) {
scope.$apply(function() {
$log.info(evt);
$log.info(e2);
$log.info("in mousemove - " + element.text());
scope.$parent.currentLetter = element.text();
});
});
}
};
});
I have tried the ng-touch library but it does not support vertical touch movements, amazingly. Any help would be massively appreciated at this point . . .

This is a limitation of touchmove currently, and I'm afraid there's no really good answer.
The best solution is to bind touchmove to a parent element and then calculate which child element is under the current touch point. It's answered in a jQuery context here Crossing over to new elements during touchmove.
You'll need to cycle through each of the child elements on the parent and check if the touch point is within their bounds.

Use Hammer.JS, it's the best I know. Angular-Hammer can be easily modified to support version 2.

Related

Change the view based on screen size

I am aware of a similar question being asked before:
Change the templateUrl of directive based on screen resolution AngularJS
This was first asked over a year ago and since then AngularJS got changed a bit. I am curious to find out if there are any other ways to achieve something similar as I haven't found many information about templateUrl swapping, so maybe I am barking up the wrong tree here.
I have a single page app without any routes.
html:
<body ng-app="App">
// lots of html same for both desktop/mobile
<my-dir></my-dir>
// even more here
</body>
template 1:
<p>Mobile</p>
template 2:
<p>Desktop</p>
I would like to render template 1 when the screen goes below 700px and template 2 otherwise. The templates change just what is inside my-dir directive. For example Template 1 renders list and template 2 renders table.
Another requirement would be to make it responsive if possible(aka templates would change as you resize the window)
At the moment I can use the solution from the above questions but are there any other ways to do it?
In your controller:
$scope.includeDesktopTemplate = false;
$scope.includeMobileTemplate = false;
var screenWidth = $window.innerWidth;
if (screenWidth < 700){
$scope.includeMobileTemplate = true;
}else{
$scope.includeDesktopTemplate = true;
}
html template:
<body ng-app="App">
<p ng-if="includeMobileTemplate">Mobile</p>
<p ng-if="includeDesktopTemplate">Desktop</p>
</body>
Hope it helps
You can add window resize and scroll event listener on my-dir directive:
angular.module("App").directive('myDir', ['$window', '$timeout', function($window, $timeout){
return {
restrict: 'EA',
scope: {},
template:'<div>
<p ng-if="showFirstTemplate">Mobile</p>
<p ng-if="showSecondTemplate">Desktop</p>
</div>',
link: function(scope, element, attr){
function checkTemplateVisible(event){
//use $timeout to make sure $apply called in a time manner
$timeout(function(){
//pageYoffset is equal to window scroll top position
if($window.pageYOffset > 700){
scope.showFirstTemplate = true;
scope.showSecondTemplate = false;
}else{
scope.showFirstTemplate = false;
scope.showSecondTemplate = true;
}
})
})
//scroll event make sure checkTemplateVisible called on browser scrolling
$window.on('scroll', checkTemplateVisible)
//resize event make sure checkTemplateVisible called on browser resizing
$window.on('resize', checkTemplateVisible)
}
}
}])

Mapbox map in ui-bootstrap tab not loading tiles

I'm trying to put a mapbox map inside an angular-ui-bootstrap tab, and it seems that some/most of the tiles are not getting loaded upon initialization, and are not being requested as you pan around on the map. Outside of the ui-bootstrap tabset, the maps work just fine.
No errors are being thrown, but looking at the requests for the tiles, many of them are just not being requested for some reason. I'm not even sure how to debug this one.
Any ideas as to what might be going on?
Here is a plunkr showing the issue
And here is an example angular app that will show the problem
var app = angular.module('app', ['ui.bootstrap'])
app.controller('mapCtrl', ['$scope', '$timeout', function($scope, $timeout) {
$scope.val = 123
}]);
app.directive('myMap', function() {
return {
restrict: 'E',
template: "<div id='map_container'></div>",
link: function ($scope, elem, attrs) {
mapDiv = elem.find('#map_container')
L.mapbox.accessToken = 'pk.eyJ1IjoicmVwdGlsaWN1cyIsImEiOiJlSWZtN1hZIn0.FfT3RxbfRYv4LIjBxXG5fw';
var map = L.mapbox.map(mapDiv[0], 'examples.map-i86nkdio')
.setView([40, -74.50], 9);
}
};
});
The map gets initialized when the mapcontainer is not visible, that's why it fails. You're on the right path with calling invalidateSize but you need to do that when the tab becomes visible. I see you've already setup an event which you could hook into in your directive link function:
$scope.$on('tabSelect:map', function (t) {
$timeout(function () {
map.invalidateSize(true);
});
});
It doesn't work without the timeout. It needs some sort of delay so the tab is complete visible before firing invalidateSize. Here's an updated Plunker: http://plnkr.co/edit/gzwx2pZ1GjBZDE8Utxfl?p=preview

AngularJS template switching

Our client wants a responsive website, but he wants to change and move so much content that we will run into bootstrap limitations.
With bootstrap you can show and hide blocks and move them around with offset, but somehow it has it's limitations. It is a demanding client that will not respect such limitations so we are looking for other options.
To avoid creating duplicate content and still have the ability to give the mobile/desktop experience our team came up with AngularJS.
Our JSON data and Angular controllers can stay the same, but we only need to switch views if it is on mobile/tablet/desktop.
Is there a good stable solution to get this working?
And can we test it like we test responsive design by resizing the browser, or is useragent detection the only solution?
That would be a pain during testing, since we need then many devices or emulators to test.
You can create a custom directive for this.
app.directive('responsiveTemplate', function() {
return {
restrict: 'E',
template: '<ng-include src="template"></ng-include>',
scope: true,
link: function(scope, elem, attr) {
var mobile = attr.mobile;
var desktop = attr.desktop;
scope.template = desktop;
$(window).resize(function() {
if (windowSizeIsDesktop() && scope.template != desktop) {
scope.template = desktop;
scope.$apply();
}
else if (windowSizeIsMobile() && scope.template != mobile) {
scope.template = mobile;
scope.$apply();
}
});
}
}
})
Use as an element
<responsive-template desktop="desktop.html" mobile="mobile.html"></responsive-template>
I have not defined the windowSize functions though they are trivial to implement
I'd probably just use ng-if for this, but I'd make sure you need it first and can't simply use css / media queries for what you're describing. Here's an example of the ng-if logic:
<body ng-app="myApp">
<div ng-controller="ctrl" >
<div ng-if="isWide()">
<p>Wide Content</p>
</div>
<div ng-if="!isWide()">
<p>Narrow Content</p>
</div>
</div>
</body>
And the js:
angular.module('myApp', []).controller('ctrl', function($scope, $window) {
$scope.isWide = function() {
return $window.innerWidth > 500; //your breakpoint here.
}
angular.element($window).on('resize', angular.bind($scope, $scope.$apply));
});
http://jsfiddle.net/gq2obdcq/8/
Just drag the split pane to see the results in the fiddle.
$routeProvider.
when('/:menu/:page', {
controller:HandleCtrl,
template:'<div ng-include="templateUrl">Loading...</div>'
}).
Combined with:
function HandleCtrl($scope, $routeParams){
$scope.templateUrl = $routeParams.menu +'/'+ $routeParams.page + '.html'
}
Would this be safe?
Inside the controller I can decide what html file I want to use as template

How can I hide an element when the page is scrolled?

Ok, I'm a little stumped.
I'm trying to think the angular way coming from a jQuery background.
The problem:
I'd just like to hide a fixed element if the window is not scrolled. If someone scrolls down the page I would like to hide the element.
I've tried creating a custom directive but I couldnt get it to work as the scroll events were not firing. I'm thinking a simple controller like below, but it doesnt even run.
Controller:
.controller('MyCtrl2', function($scope,appLoading, $location, $anchorScroll, $window ) {
angular.element($window).bind("scroll", function(e) {
console.log('scroll')
console.log(e.pageYOffset)
$scope.visible = false;
})
})
VIEW
<a ng-click="gotoTop()" class="scrollTop" ng-show="{{visible}}">TOP</a>
LIVE PREVIEW
http://www.thewinetradition.com.au/new/#/portfolio
Any ideas would be greatly appreciated.
A basic directive would look like this. One key point is you'll need to call scope.$apply() since scroll will run outside of the normal digest cycle.
app = angular.module('myApp', []);
app.directive("scroll", function ($window) {
return function(scope, element, attrs) {
angular.element($window).bind("scroll", function() {
scope.visible = false;
scope.$apply();
});
};
});
I found this jsfiddle which demonstrates it nicely http://jsfiddle.net/88TzF/

Perform task after model's DOM is displayed in view

I have a code snippet in my content which is a model fetched from http. I am using syntax highlighter to prettify the code. So I need to call a javascript function as soon as the DOM is updated for that particular model.
Here is a sample code to make it clear. I am using alert to demonstrate it. In my project I would use a third party plugin which will find matching dom elements and remodel them.
Here,
I want the alert to occur after the list is displayed
jsfiddle :
http://jsfiddle.net/7xZde/2/
My controller has something like this.
$scope.items = Model.notes();
alert('test');
alert comes even before the items list is shown, I want it after the list is displayed.
Any hint to help me achieve this.
We need to use $timeout ,
$scope.items = Model.notes();
$timeout(function () {
alert('test');
})
Yeah it was silly , $timeout seemed to be a misnomer to me. I am 2 days old to angularjs . Sorry for wasting your time.
Lucky for you, I wanted to do the exact same thing. Mutation observers are the path forward, but if you need backwards compatibility with older browsers, you'll need a bit more code than this.
Working plunker for Firefox, Chrome, and Safari.
Javascript:
var app = angular.module('plunker', [])
.controller('MainCtrl', function($scope) {
$scope.name = 'World';
})
.directive('watchChanges', function ($parse, $timeout) {
return function (scope, element, attrs) {
var setter = $parse(attrs.watchChanges).assign;
// create an observer instance
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$timeout(function () {
var text = angular.element('<div></div>').text(element.html()).text();
setter(scope, text);
});
});
});
// configuration of the observer:
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true
};
// pass in the target node, as well as the observer options
observer.observe(element[0], config);
};
});
HTML:
<body ng-controller="MainCtrl">
<div watch-changes="text">
<p>Hello {{ name }}</p>
<label>Name</label>
<input type="text" ng-model="name" />
</div>
<pre>{{text}}</pre>
</body>

Resources