Action helper click events don't fire on touch devices #605

Closed
ebryn opened this Issue Mar 21, 2012 · 13 comments

Comments

Projects
None yet
@ebryn
Member

ebryn commented Mar 21, 2012

No description provided.

@wagenet

This comment has been minimized.

Show comment
Hide comment
@wagenet

wagenet Apr 17, 2012

Member

The events do fire, but only if the element is one that responds to click events. This is technically not a bug, but it's something we should improve.

Member

wagenet commented Apr 17, 2012

The events do fire, but only if the element is one that responds to click events. This is technically not a bug, but it's something we should improve.

@JamesHight

This comment has been minimized.

Show comment
Hide comment
@JamesHight

JamesHight Jun 11, 2012

Here is a patch I'm using for handling click events on touch devices.
It may be a good starting point.

(function($){       
    var touch = {       
        start: false, // has the touchstart event been triggered and is it still a valid click event?
        // starting coordinates of touch event
        x: null, 
        y: null
    };

    Ember.EventDispatcher.reopen({
        setupHandler: function(rootElement, event, eventName) {
            var self = this;

            rootElement.delegate('.ember-view', event + '.ember', function(evt, triggeringManager) {        
                // Track touch events to see how far the user's finger has moved
                // If it is > 20 it will not trigger a click event
                switch(evt.type) {
                    // Remember our starting point
                    case 'touchstart':                  
                        touch.start = true;
                        touch.x = evt.originalEvent.touches[0].clientX;
                        touch.y = evt.originalEvent.touches[0].clientY;
                        break;

                    // Monitor touchmove in case the user moves their finger away and then back to the original starting point
                    case 'touchmove':
                        if (touch.start) {
                            var moved = Math.max(Math.abs(evt.originalEvent.touches[0].clientX - touch.x), 
                                            Math.abs(evt.originalEvent.touches[0].clientY - touch.y));
                            if (moved > 20)
                                touch.start = false;
                        }
                        break;

                    // Check end point
                    case 'touchend':
                        if (touch.start) {
                            var moved = Math.max(Math.abs(evt.originalEvent.changedTouches[0].clientX - touch.x), 
                                        Math.abs(evt.originalEvent.changedTouches[0].clientY - touch.y));
                            if (moved < 20) {
                                // Prevent touchend event so the simulated click event is not triggered
                                evt.preventDefault();           
                                // All tests have passed, trigger click event
                                $(evt.target).click();
                            }
                            touch.start = false;
                        }                           
                        break;
                }

                // END touch code

                var view = Ember.View.views[this.id],
                result = true, manager = null;

                manager = self._findNearestEventManager(view,eventName);

                if (manager && manager !== triggeringManager) {
                    result = self._dispatchEvent(manager, evt, eventName, view);
                } else if (view) {
                    result = self._bubbleEvent(view,evt,eventName);
                } else {
                    evt.stopPropagation();
                }

                return result;
            });

            rootElement.delegate('[data-ember-action]', event + '.ember', function(evt) {
                var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'),
                    action   = Ember.Handlebars.ActionHelper.registeredActions[actionId],
                    handler  = action.handler;

                if (action.eventName === eventName) {
                    return handler(evt);
                }
            });
        }
    });

})(jQuery);

Here is a patch I'm using for handling click events on touch devices.
It may be a good starting point.

(function($){       
    var touch = {       
        start: false, // has the touchstart event been triggered and is it still a valid click event?
        // starting coordinates of touch event
        x: null, 
        y: null
    };

    Ember.EventDispatcher.reopen({
        setupHandler: function(rootElement, event, eventName) {
            var self = this;

            rootElement.delegate('.ember-view', event + '.ember', function(evt, triggeringManager) {        
                // Track touch events to see how far the user's finger has moved
                // If it is > 20 it will not trigger a click event
                switch(evt.type) {
                    // Remember our starting point
                    case 'touchstart':                  
                        touch.start = true;
                        touch.x = evt.originalEvent.touches[0].clientX;
                        touch.y = evt.originalEvent.touches[0].clientY;
                        break;

                    // Monitor touchmove in case the user moves their finger away and then back to the original starting point
                    case 'touchmove':
                        if (touch.start) {
                            var moved = Math.max(Math.abs(evt.originalEvent.touches[0].clientX - touch.x), 
                                            Math.abs(evt.originalEvent.touches[0].clientY - touch.y));
                            if (moved > 20)
                                touch.start = false;
                        }
                        break;

                    // Check end point
                    case 'touchend':
                        if (touch.start) {
                            var moved = Math.max(Math.abs(evt.originalEvent.changedTouches[0].clientX - touch.x), 
                                        Math.abs(evt.originalEvent.changedTouches[0].clientY - touch.y));
                            if (moved < 20) {
                                // Prevent touchend event so the simulated click event is not triggered
                                evt.preventDefault();           
                                // All tests have passed, trigger click event
                                $(evt.target).click();
                            }
                            touch.start = false;
                        }                           
                        break;
                }

                // END touch code

                var view = Ember.View.views[this.id],
                result = true, manager = null;

                manager = self._findNearestEventManager(view,eventName);

                if (manager && manager !== triggeringManager) {
                    result = self._dispatchEvent(manager, evt, eventName, view);
                } else if (view) {
                    result = self._bubbleEvent(view,evt,eventName);
                } else {
                    evt.stopPropagation();
                }

                return result;
            });

            rootElement.delegate('[data-ember-action]', event + '.ember', function(evt) {
                var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'),
                    action   = Ember.Handlebars.ActionHelper.registeredActions[actionId],
                    handler  = action.handler;

                if (action.eventName === eventName) {
                    return handler(evt);
                }
            });
        }
    });

})(jQuery);
@krisselden

This comment has been minimized.

Show comment
Hide comment
@krisselden

krisselden Sep 1, 2012

Member

see my comments in #586 for a much simpler solution

Member

krisselden commented Sep 1, 2012

see my comments in #586 for a much simpler solution

@tomdale

This comment has been minimized.

Show comment
Hide comment
@tomdale

tomdale Sep 20, 2012

Member

For now, I think it's the responsibility of the application developer to make the element tappable on iOS devices. If you stick to links, this should work fine. If this is tripping people up, I'd accept a pull request adding additional documentation to the {{action}} helper.

Member

tomdale commented Sep 20, 2012

For now, I think it's the responsibility of the application developer to make the element tappable on iOS devices. If you stick to links, this should work fine. If this is tripping people up, I'd accept a pull request adding additional documentation to the {{action}} helper.

@tomdale tomdale closed this Sep 20, 2012

@ppcano ppcano referenced this issue in emberjs-addons/ember-touch Nov 7, 2012

Open

Enable an easy way to map tapEnd event to click #12

@topfunky

This comment has been minimized.

Show comment
Hide comment
@topfunky

topfunky Dec 28, 2012

Contributor

This is painful, especially since it can be solved by adding a meaningless href to any link.

IMHO supporting iOS out of the box should be a value of this project.

Especially since it took several different Google searches to find this thread.

Contributor

topfunky commented Dec 28, 2012

This is painful, especially since it can be solved by adding a meaningless href to any link.

IMHO supporting iOS out of the box should be a value of this project.

Especially since it took several different Google searches to find this thread.

@demongloom

This comment has been minimized.

Show comment
Hide comment
@demongloom

demongloom Jan 8, 2013

Agree with topfunky, we need a more convenient way to bind on both click and touch

Agree with topfunky, we need a more convenient way to bind on both click and touch

@FoxGit

This comment has been minimized.

Show comment
Hide comment
@FoxGit

FoxGit Feb 14, 2013

I also agree with @topfunky. Being able to test certain actions both on a desktop browser and an iPad would be really helpful.

FoxGit commented Feb 14, 2013

I also agree with @topfunky. Being able to test certain actions both on a desktop browser and an iPad would be really helpful.

@arcreative

This comment has been minimized.

Show comment
Hide comment
@arcreative

arcreative May 7, 2013

I just found a wonderful fix by mistake. I'm doing most of my testing in Chrome before deploying to Android/iOS via PhoneGap. I opted for jquery mobile's touch implementation (using a custom build with ONLY touch enabled) because it seemed a little more simple to implement than ember-touch. I used the following to avoid unwanted double-click behavior due to the tap AND click events registering:

App = Ember.Application.extend({
    customEvents: {
        tap: "click",
        click: null //Prevent tap from registering as two clicks
    }
});

This registers the tap as a click and the click as nothing, so the double-click nonsense I was encountering using tap events is no longer an issue. It has the added advantage of working with the standard action helper as well on all platforms I'm using, so someone might want to give this a shot to see if it works. No more tapEnd or touchend to worry about, this has been a 100% perfect implementation for me so far.

This was originally in response to #586

I just found a wonderful fix by mistake. I'm doing most of my testing in Chrome before deploying to Android/iOS via PhoneGap. I opted for jquery mobile's touch implementation (using a custom build with ONLY touch enabled) because it seemed a little more simple to implement than ember-touch. I used the following to avoid unwanted double-click behavior due to the tap AND click events registering:

App = Ember.Application.extend({
    customEvents: {
        tap: "click",
        click: null //Prevent tap from registering as two clicks
    }
});

This registers the tap as a click and the click as nothing, so the double-click nonsense I was encountering using tap events is no longer an issue. It has the added advantage of working with the standard action helper as well on all platforms I'm using, so someone might want to give this a shot to see if it works. No more tapEnd or touchend to worry about, this has been a 100% perfect implementation for me so far.

This was originally in response to #586

@manufaktor

This comment has been minimized.

Show comment
Hide comment
@manufaktor

manufaktor Sep 25, 2013

this little CSS hack has worked for me:

[data-ember-action] {
  cursor: pointer;
}

this little CSS hack has worked for me:

[data-ember-action] {
  cursor: pointer;
}
@elsurudo

This comment has been minimized.

Show comment
Hide comment
@elsurudo

elsurudo Mar 17, 2014

@manufaktor Thanks for the quick fix, seems to be working nicely.

@manufaktor Thanks for the quick fix, seems to be working nicely.

tute added a commit to tute/website that referenced this issue Jul 1, 2014

@tute tute referenced this issue in emberjs/website Jul 1, 2014

Merged

Add documentation about `action` #1585

tute added a commit to tute/website that referenced this issue Jul 1, 2014

@ming-codes

This comment has been minimized.

Show comment
Hide comment
@ming-codes

ming-codes Sep 27, 2014

Contributor

Changing the tag to <button> seems to help. You can style the button to not look like a button.

Contributor

ming-codes commented Sep 27, 2014

Changing the tag to <button> seems to help. You can style the button to not look like a button.

@stefanpenner

This comment has been minimized.

Show comment
Hide comment
@stefanpenner

stefanpenner Sep 27, 2014

Member

@Lightblade just ensure the clickable entity has cursor: pointer; (this has nothing to do with ember but how touch devices trigger/simulate click events).

Member

stefanpenner commented Sep 27, 2014

@Lightblade just ensure the clickable entity has cursor: pointer; (this has nothing to do with ember but how touch devices trigger/simulate click events).

@Willibaur

This comment has been minimized.

Show comment
Hide comment
@Willibaur

Willibaur Mar 31, 2017

I had the same issue today but I solved that by adding td onclick='' maybe it can works for some else

I had the same issue today but I solved that by adding td onclick='' maybe it can works for some else

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment