Eria Eria - 2 months ago 65
Javascript Question

ui-select input does not get focus on click : conflict with angular-touch

I have a problem on an AngularJS application that uses both ui-select and angular-touch.

On Safari, using a mobile device such as an iPad or an iPhone, when I click on the text input field of the ui-select directive, the virtual keyboard wont open, the input does not get the focus.

I discovered that it is angular-touch that causes the problem, since as soon as I remove the dependency from the app, everything works fine again.

HTML

<body ng-controller="DemoCtrl">
<ui-select multiple
theme="select2"
ng-model="multipleDemo.selection"
reset-search-input="true"
style="min-width: 300px;">
<ui-select-match placeholder="Enter an adress...">
{{$item.formatted_address}}
</ui-select-match>
<ui-select-choices repeat="address in addresses track by $index"
refresh="refreshAddresses($select.search)"
refresh-delay="250">
<div ng-bind-html="address.formatted_address | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
<p>Selected : {{multipleDemo.selection | selectionFilter}}</p>

</body>


JS

var app = angular.module('demo', ['ngSanitize', 'ui.select', 'ngTouch']);

app.filter('selectionFilter', function() {
// Not important... see the plunker for detail.
});

app.controller('DemoCtrl', function($scope, $http) {
$scope.multipleDemo = {};
$scope.multipleDemo.selection = [];

$scope.address = {};
$scope.refreshAddresses = function(address) {
var params = {address: address, sensor: false};
return $http.get(
'http://maps.googleapis.com/maps/api/geocode/json',
{params: params}
).then(function(response) {
$scope.addresses = response.data.results;
});
};

});


http://plnkr.co/edit/dFBQ4si6hLMP1S9dal7m?p=preview

Does someone have an idea of what I can do to prevent ngTouch from causing the problem ? I can't just remove ngTouch from the dependencies : I need it elsewhere.

update 16/09

The problem seems to come from this part of angular-touch (1.4.8) :

element.on('touchend', function(event) {
var diff = Date.now() - startTime;

// Use jQuery originalEvent
var originalEvent = event.originalEvent || event;
var touches = (originalEvent.changedTouches && originalEvent.changedTouches.length) ?
originalEvent.changedTouches :
((originalEvent.touches && originalEvent.touches.length) ? originalEvent.touches : [originalEvent]);
var e = touches[0];
var x = e.clientX;
var y = e.clientY;
var dist = Math.sqrt(Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2));

if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
// Call preventGhostClick so the clickbuster will catch the corresponding click.
preventGhostClick(x, y);

// Blur the focused element (the button, probably) before firing the callback.
// This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
// I couldn't get anything to work reliably on Android Chrome.
if (tapElement) {
tapElement.blur();
}

if (!angular.isDefined(attr.disabled) || attr.disabled === false) {
element.triggerHandler('click', [event]);
}
}

resetState();
});


If I disable the condition, adding
&& false
for example, the input gets focus on click.
I need to find a way to disable this event binding on the input from outside angular-touch lib.

Answer

The directive below, applied on the ui-select parent directive, solved the problem :

app.directive('fixFocusOnTouch', function(){
  return {
    restrict: 'A',
    controller: function( $element ){
      /*
      Usually, event handlers binding are made in the link function.
      But we need this handler to be executed first, so we add it in the controller function instead.
       */
      var inputElement = $element[0].querySelector( 'input.ui-select-search' );
      angular.element( inputElement ).bind( 'touchend', function( event ){
        event.stopImmediatePropagation();
      });
    }
  }
});

It kills the current propagation of touchend event before the handler added by angular-touch can execute (see the "update" part of the question).

I could not make it work on Plunkr, but I'll try again some time to provide a working example.

Usage :

<ui-select multiple fix-focus-on-touch
           theme="select2"
           ng-model="multipleDemo.selection"
           reset-search-input="true"
           style="min-width: 300px;">
  <ui-select-match placeholder="Enter an adress...">
    {{$item.formatted_address}}
  </ui-select-match>
  <ui-select-choices repeat="address in addresses track by $index"
                     refresh="refreshAddresses($select.search)" 
                     refresh-delay="250">
    <div ng-bind-html="address.formatted_address | highlight: $select.search"></div>
  </ui-select-choices>
</ui-select>