THE BLOG

News, tips and tricks from 2Gears

CSS trick: Sencha Touch list tap highlight with immediate deselect

Posted · 5 Comments
css3-logo 150

Just a quick trick this time. Sencha Touch Lists can be used for a lot of use cases. In some of those use cases we want to allow a user to tap a record and register that tap, but then immediately deselect the item (and show an action sheet for instance). With a ‘select’ listener this is no problem… but the user loses visual feedback of the tap. The solutions out there were not sufficient for us so I created a simple CSS trick for doing just that.

 

setTimeout… bah

The easy way would be to create a listener on the tap event and then schedule a deselect on the list with a setTimeout. These two solutions I see around as the suggested solutions the most:

// Clear all selections on the list after 500 ms
listeners: {
	itemtap: function(list, idx, item, e) {
		// Clear the selection after 500ms
		setTimeout(function() {
			list.deselectAll();
		}, 500);
	}
}

This solution can lead to race conditions because when the user quickly taps on 2 records after each other, the deselectAll of the first selected record can then deselect the second one too. When the user clicks the two items within 400ms, the first record is deselected on selection of the first, but the second one is deselected after 100ms by the timeOut scheduled by the first selection. We could of course do a clearTimeout first but then we also need to keep track of the timer id… just a hassle.

Another one:

// Clear the selected index on the list after 500ms
listeners: {
	itemtap: function(list, idx, item, e) {
		// Clear the selection after 500ms
		setTimeout(function() {
			list.deselect(idx);
		}, 500);
	}
}

This one is a little better since only the selected index is deselected. When the user quickly taps a record twice the same problem occurs as with the previous example, the second selection is cleared after 100ms by the first selection’s timeOut. Again we can store the timer id and clear it first, but I am not that much a fan of too much timeOuts floating around when they are not needed.

A better solution: CSS transition

When we select the list item, it is decorated with a new CSS class that takes care of the styling. We want to allow all the normal tap/select events to fire (so the disableSelection configuration will not work) and then immediately deselect the item. Deselecting the item is trivial:

listeners: {
	itemtap: function(list, idx, item, e) {
		// Clear the selection immediately
		list.deselect(idx);
	}
}

Great, that deselects our item, but what about the tap highlight feedback we wanted. The item is decorated with the ‘selected’ class to highlight it, but that class is then immediately removed again. Well, herein lies the solution: CSS transition delay. When we tell CSS to use a transition for certain properties of the ‘selected’ class and implement a transition delay we get exactly what we want, just add this Sass snippet to your sass files and compile the CSS:

.x-list-item {
	background-color: white;

	-webkit-transition: background-color, color 150ms linear;
	-webkit-transition-delay: 250ms;

	&.x-item-pressed, &.x-item-selected {
		color: #fff;
		background-color: $list-active-color;
		-webkit-transition: background-color, color 0ms linear;
		-webkit-transition-delay: 0s;
	}
}

How this works is that we define a CSS transition (I have omited the custom -moz- like alternatives) on the background color and color of the .x-list-item class. The transition fades the background color and text color to their new values in 150ms (making it more fluid than just flipping it). It also incorporates a transition delay of 250ms. So when the background-color and color of the .x-list-item become active they only change after 250ms and transition for 150ms. Now when the user selects an item, the item is decorated with the .x-item-selected class. That class also contains a transition on the background-color and color, but both the transition and delay are set to 0ms. This makes sure that the item is instantaneously selected, and not with a delay and/or fade.
So in short what happens is:

  • user selects item
  • item is decorated with .x-item-selected
    `-> item class instantaneously ‘transitions’ the ‘selected’ styling
  • the item is deselected by the handler
    `-> the default styling of the x-list-item is delayed by 250ms and then transitions to the default styling in 150ms

So there you go, instantaneous deselection of list items while still providing visual feedback to the user, emulating a delayed deselect. All without using JavaScript Timers, just pure CSS.

Consider sharing this if it helps you. Cheers

5 Responses to "CSS trick: Sencha Touch list tap highlight with immediate deselect"
  1. Or says:

    Which version of Sencha Touch are you using?
    It doesn’t work for me in 2.2.1.
    When the itemtap event is called, the record is not selected yet, so deselect does nothing.

    • Rob Boerman says:

      Yes, this works in Sencha Touch 2.2.1. Make sure you are not preventing the selection somehow. To test it out in 2.2.1 you can go to:
      http://docs.sencha.com/touch/2.2.1/touch-build/examples/list/index.html

      Open your Chrome developer console and dump the folowing into the console. The CSS is just the SASS output of the SCSS snippet I showed above.

      Ext.versions.touch.version;
      Ext.ComponentQuery.query('list')[0].on({
      		itemtap: function(list, idx, item, e) {
      		// Clear the selection immediately
      		list.deselect(idx);
      	}
      });
      
      var css = [
      ".x-list-item {",
        "background-color: white;",
        "-webkit-transition: background-color, color 150ms linear;",
        "-webkit-transition-delay: 250ms; }",
        ".x-list-item.x-item-pressed, .x-list-item.x-item-selected {",
          "color: #fff;",
          "background-color: blue;",
          "-webkit-transition: background-color, color 0ms linear;",
          "-webkit-transition-delay: 0s; }"
      ].join('');
      
      var node = document.createElement('style');
      node.innerHTML = css;
      document.body.appendChild(node);
      

      The odd rows (grey), don’t highlight properly in this example but you can fix that yourselve :) Clicking on the white rows highlights them shortly with a CSS transition

      • Or says:

        Thanks, it works.

        I tried registering to the itemtap event in a controller using the control config, and that’s why it hadn’t worked for me.

        Apparently, using the listeners config of a view or using “on”, is different from using the control config of a controller (probably different event order).

        Can you please try using a controller and validate it doesn’t work? I’ll then feel confident enough to report a new bug on the Sencha Touch forum.

        Thanks

        • Rob Boerman says:

          Hi,

          Controllers use exactly the same method of listening to events as I did in my example. They use component queries to get a reference to components in runtime and listen to events fired by those components. The itemtap event the List fires is just as visible to a controller as it is to the internals of the component itself. Unfortunately right now I dont have time to create an example with a Controller but it should be pretty easy to do using

          Ext.define('Foo.controller.Bar', {
          	extend: 'Ext.app.Controller',
          	requires: [
          	],
          	config: {
          		refs: {
          		},
          		control: {
          			'list': {
          				itemtap: function(list, idx, item, e) {
          					// Clear the selection immediately
          					list.deselect(idx);
          				}
          			}
          		}
          	}
          });
          

          Good luck

          • Or says:

            Hi,

            I’m aware it’s possible to listen to the itemtap event using a controller, and that’s exactly what I did.

            I’m also aware that listening to the event using “on” SHOULD be exactly the same as using a controller.

            I wanted you to try it yourself because I suspected there was a bug in Sencha Touch that caused a difference in the event order between events registered with “on” and those registered with the control config of a controller.

            As I said, when I used a controller your code didn’t work but when I changed it to use “on”, it did.

Leave a Reply to Or Cancel reply

Your email address will not be published. Required fields are marked *

three × 5 =

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>