Users misunderstand "Remove” checkbox on cart page

ezra-g - July 23, 2009 - 21:00
Project:Ubercart
Version:6.x-2.x-dev
Component:Code
Category:feature request
Priority:normal
Assigned:Unassigned
Status:active
Description

GVS recently did some usability testing on a client site that included Ubercart and UC_Signup, and identified potential usability issues that apply to Ubercart. I'm posting those here so others can weigh in on the proposed improvements.

When viewing their cart contents two of four participants clicked the "Remove" box next to the item, as if to confirm the selection.

Potential Improvements
- Replace the remove checkbox with a "remove" button, like amazon does (this eliminates the need to also click "update cart" when changing removing a single item)
Amazon.com Shopping Cart

- Move the checkbox to the left of the quantity field, so that it is more visually connected to the idea of quantity.
- Call attention to the purpose of the box by a question mark so it reads "Remove?"

#1

rszrama - July 25, 2009 - 00:44

I had a similar thought when looking at Lullabot's old Shopify store. "Unfortunately," they've switched to Ubercart, so I can't reference it any more. Will have to find another Shopify store... their cart page absolutely rocked. : P

#2

dman - July 28, 2009 - 20:38

A checkbox to signal deletion is a UI wrong. Only webmails or phpmyadmin can get away with that.
On other things - (like the Drupal node-edit "menu" widget) it's backwards :-B
That said .. A button makes it hard to do more than one thing at once.

Maybe we need AJAX. It actually IS a checkbox for degradation but UI script replaces it with a button and action?

#3

Zach Harkey - July 29, 2009 - 20:44

The uc_ajax_cart module provides this exact functionality and it works awesome.

Unfortunately it also hijacks the 'add to cart' button with an unhelpful ajax variation that updates the cart total without leaving the product page. While this sounds good in theory, in practice the user can't tell that anything has happened and they just continue to click the button, which adds more and more products to the cart. The user then has to locate the View Cart button somewhere in the navigation or cart block.

I much prefer the default method of sending the user directly to the shopping cart. In fact, I don't see why they couldn't just go straight to checkout.

#4

echoz - July 30, 2009 - 19:41

+1 on - Replace the remove checkbox with a "remove" button, eliminates second step clicking "update cart" to remove item. As a user that is not confused what the checkbox does, I'm always impressed by a cart that lets me remove with one click.

#5

peach - August 6, 2009 - 08:24

+1 on moving any deletion control close to the quantity indication.

#6

justinlevi - August 18, 2009 - 21:38

+1

This is a really poor default UI element

#7

xurizaemon - August 24, 2009 - 23:12
Title:Users miunderstand "Remove” checkbox on cart page» Users misunderstand "Remove” checkbox on cart page

Attached is a module which implements functionality like this. The code isn't perfect, and I'd really appreciate feedback on a couple of points.

The main challenge for me was making the remove button (which I've made an image_button) store and return the data pointing to which item in the cart should be deleted. I think that some aspect of the tapir table and form generation code was confusing me - I added a series of elements of the form $table['remove_button']['remove_button_0']['#value'] = 0; but the returned value was always the value stored in the final button on the form.

Anyway, hacked around that with an ugly function which inspects the posted data and does the dirty work.

I would be really happy to rewrite this for inclusion in uc_cart, rather than as an external module, so it can be presented as an optional method for removing items. For some shops, this makes a lot more sense. Others may prefer to keep the current setup. Some crazy folks may even want both onscreen at once (now, THAT would cause misunderstanding).

(Also, I should make the remove button a bit more themeable - input welcome on the best way to do this - but CSS should work fine for now.)

AttachmentSize
529110-uc_removebutton_module.tgz 3.48 KB

#8

xurizaemon - August 24, 2009 - 23:25

Another issue which needs to be resolved before this is ready for primetime is that changing the Qty of one item, then deleting another will revert the changed Qty of the first.

Screenshot of the remove button placement below -

AttachmentSize
529110-uc_removebutton-1.png 32.34 KB

#9

xurizaemon - September 23, 2009 - 04:31

Updated code for this module. Really keen for some feedback on any improved methods to achieve the same result.

AttachmentSize
529110-uc_removebutton-r1150.tgz 3.48 KB

#10

torgosPizza - September 23, 2009 - 23:19

If you want this to be included in core, you should roll your changes into a patch :) I doubt it'd make it into 2.0-stable, but maybe the Uberdudes would be keen to roll it in later on? It does seem to make more sense than a checkbox.

Also you can AJAXify the cart table so that when a cart is removed or updated the user never has to see the page refresh. And of course it'd degrade so that without JS the page does reload.

+1 from me for getting this into core.. at some point!

#11

xurizaemon - September 24, 2009 - 00:15

@torgos - I understand, but this code is miles from being ready to merge into core.

In particular there's the issue raised in comment #7 which I had to work around using some hella ugly code to inspect which remove button was clicked.

Maybe you can point me in the right direction. In short -

* We set a custom submit handler uc_removebutton_remove_item() for the remove button.
* We set a value on that image submit button, which is the index of the cart item to remove.
* But in the custom submit handler, I found I couldn't extract the value using $id = $form_state['clicked_button']['#value'] ; - this would ALWAYS return the index of the last remove button on the form.
* So instead I've got a very ugly _uc_removebutton_identify_removed_item() function to pull the clicked button out of the post values.

<?php
/**
* Implement hook_form_alter()
*
* We add an extra column to the view cart screen, which presents a
* "Remove" button for each entry to replace the remove checkbox.
*
* For compatability with UC we conceal the original "Remove" column
* in hook_tapir_table_alter by setting the access property of that
* column to FALSE.
*/
function uc_removebutton_form_alter(&$form, &$form_state, $form_id) {
  switch (
$form_id ) {
    case
'uc_cart_view_form' :
      foreach (
$form['items'] as $k => $v ) {
       
// we are iterating thru the view cart form, and interacting
        // with items only if they are a table row (have an integer
        // key) and are a product row (have a remove column)
       
if ( is_integer($k) && isset($v['remove']) ) {
         
$nid = $v['nid']['#value'] ;
         
// add a remove button in a new column 'remove_button' - if
          // we overwrite 'remove' then we'll trigger the removal of
          // all products, because uc_cart_update_item_object() only
          // checks to see if 'remove' is set at all
         
$form['items'][$k]['remove_button']['data'] = array(
           
'remove_button_'.$k => array(
             
'#type' => 'image_button',
             
'#src'  => drupal_get_path('module','uc_removebutton') . '/delete.png',
             
'#submit' => array( 'uc_removebutton_remove_item' ),
             
'#value' => 'id:'.$k,
             
'#id' => 'remove-button-'.$k,
            ),
          ) ;
        }
      }
      break ;
    default :
      break ;
  }
}

/**
* Callback for the remove_button element
*
* This custom submit handler will remove the item for the product
* which was selected. It will also preserve any other changes (eg to
* qty) made at the same time.
*
* Would appreciate input on whether this can handle calling the
* parent form's submit handler(s) better too (see last few lines of
* function).
*/
function uc_removebutton_remove_item( $form, &$form_state ) {
  if (
$form_state['submit_handlers'][0] == __FUNCTION__ ) {
   
// I would love for this next line to work, but for some reason it
    // always returns the value of the LAST remove button instead.
    /* $id = $form_state['clicked_button']['#value'] ; */
    // so instead we have this super-ugly function to get us through
    // until storing the value in the submit button works better.
   
$id = _uc_removebutton_identify_removed_item($form) ;
    if ( isset(
$form['items'][$id]['nid']['#value']) && isset($form['items'][$id]['data']['#value']) ) {
     
$nid  = $form['items'][$id]['nid']['#value'];
     
$data = $form['items'][$id]['data']['#value'];
      if (
$data = unserialize($data) ) {
       
$params = array(
         
'!qty' => $form['items'][$id]['qty']['#default_value'],
         
'!title' => $form['items'][$id]['title']['#value'],
        ) ;
       
$msg = t('Removed !qty x !title from your cart', $params);
       
drupal_set_message($msg);
       
uc_cart_remove_item($nid, NULL, $data);
      }
    }
  }
 
// we also want to call the submit handler for the form - not sure
  // if this is the correct method - seems wrong and doesn't update
  // product qtys
 
foreach ( $form['#submit'] as $submit_handler ) {
    if (
function_exists($submit_handler) ) {
     
$submit_handler($form, $form_state) ;
    }
  }
 
drupal_goto('cart');
}

/**
* Identify which item to remove from the cart.
*
* Super ugly function which I'd like to disown. A workaround for the
* fact that when I added multiple image buttons in the remove column
* with the name 'remove_button_N' and value set to 'N' (where N =
* 0,1,2,3,...), the button click would always return the value of the
* final button. Grrr.
*
* Only the clicked button's value will be present in the post
* values. We extract it from there and then take 'id:' off the
* front.
*
* The id: prefix is there because otherwise removing the 0th item in
* the cart stops working.
*/
function _uc_removebutton_identify_removed_item($form) {
  if ( isset(
$form['items'][0]['image']['#post']['items'] ) ) {
    foreach(
$form['items'][0]['image']['#post']['items'] as $item ) {
      if ( isset(
$item['remove_button']['data'] ) ) {
       
$removed = array_pop(array_flip($item['remove_button']['data'])) ;
        return
preg_replace('/remove_button_/','',$removed) ;
      }
    }
  }
}


?>

I'm sure there's a way to make this work. If I can turn this into code that I like the look of, I intend submitting a patch to UC.

#12

torgosPizza - September 24, 2009 - 00:19

Yeah I guess if you are being forced to use form elements it can get hairy. I'll give it a shot one of these days if I get really bored :)

#13

torgosPizza - September 24, 2009 - 18:01

Okay, here's my first pass at it. I was able to do it in less code, and only had to hack 1 core modules to do it: uc_cart.module:

Add this to uc_cart_menu():

<?php
$items
['cart/remove/%/%'] = array(
 
'title' => 'Remove a cart item',
 
'description' => 'Remove an item from the cart on click',
 
'page callback' => 'uc_cart_button_remove',
 
'page arguments' => array(2, 3),
 
'access arguments' => array('access content'),
 
'type' => MENU_CALLBACK,
);
?>

Add this block anywhere (I added it to the bottom so it was easier to find while writing it):

<?php
/**
* Remove the row from the user's cart, based on a click from the "Remove" button
*/
function uc_cart_button_remove($item, $nid) {
 
$cart = uc_cart_get_contents();
  if (
uc_product_is_product(intval($nid)) == TRUE) { // Without intval() it fails for some reason
   
$product = $cart[$item];
   
uc_cart_remove_item($nid, NULL, $product->data);
   
drupal_set_message(t(l($product->title, 'node/'. $nid).' removed from cart.'));
   
drupal_goto('cart');
  }
  else {
   
drupal_set_message(t('An error has occurred. Please contact a site administrator.'));
   
drupal_goto('cart');
  }
}
?>

Add this to uc_cart_view_form(), inside the foreach($items as $item) loop:

<?php
// The $item->nid isn't 100% necessary, but might be good to check later on.
$form['items'][$i]['remove'] = array('#value' => l("Remove", 'cart/remove/'. $i .'/'. $item->nid));
?>

And if you want you can adjust the weight of your columns within uc_cart_view_table($table) so that the "Remove" column shows up next to Qty. To do that I just made Remove have a weight of 4, and Total have a weight of 5.

Now keep in mind this is my first attempt, and from my testing it seems to work really well. I'd of course make the Remove link a button to call more attention to itself, or better yet have it tacked on underneath the Qty field, but this is as far as I got for now.

Feel free to give it a shot and if it seems solid enough to move into a patch I can do that. (I don't have the latest UC right now on my local testing computer or else I'd roll it against that.. plus I should check it out of CVS...)

#14

torgosPizza - September 24, 2009 - 05:14

Oh and here's a screenshot so you can see it in action.

AttachmentSize
Screen shot 2009-09-23 at 10.13.09 PM.png 37.24 KB
Screen shot 2009-09-23 at 10.13.30 PM.png 37.83 KB

#15

valariems - November 2, 2009 - 23:00

I found this issue page because I have received at least four emails over the last week since launching, where customers had their product "disappear" from their shopping cart. After speaking with one of them, she figured out that she had clicked the "box on the left" - the remove box - and then clicked the checkout button. Accidentally deleted the product. She thought that the checkbox was to confirm her product selection.

Would be interested in resolution to the issue. Unfortunately, I'm not a developer and can't write code to help. Was thinking some type of solution would be a confirmation popup like "are you sure you want to delete this item?" would be a solution. I'll follow along with the issue and see what happens - just wanted to chime in that my users were having issues.

#16

tcindie - November 5, 2009 - 19:30

Would replacing the checkboxes with cartlinks links be doable? Their action could be ajaxified or not, but that seems like it might be a pretty straightforward solution...

A bit of additional code would be necessary to include some single item removal handling, but it seems like that would handle the issue to me.

For reference, because Ryan mentioned it previously, I've attached a couple screenshots of some shopify carts. Looks like the only other thing we're missing really is the price/item, rather than just a quantity and subtotal for each cart item.

I think it would make sense to add a column for item price. So if I were ordering 5 baseballs that cost $3 each it wouldn't say Qty 5, $15.00 but rather it would say something like Qty 5, Price $3.00, Total $15.00

The other thing I noticed while browsing through some shopify sites is that their cart items aren't necessarily formatted as a table like ours, and the two I've referenced here, some had a separate div (I think, didn't look at the code) for each item, and price/qty/remove/etc could be anywhere in relation to the the title/photo of that item.

So ideally it would seem we need a complete overhaul of the cart (and probably the checkout and review pages as well) Most of the tables could be replaced with divs for each line item, with its associated items in their own div or span tags. The default css then could create table views as we have them now, but restyling the cart would be a dream for themers. I realize that's probably more of an ubercart 3 wishlist item, (and I'll repost some of these thoughts in the uc3 thread in the forum) but since we're talking about User interface/user experience stuff it's probably worth mentioning.

AttachmentSize
shopify-store1.jpg 51.02 KB
shopify-store2.jpg 28.9 KB

#17

asak - November 7, 2009 - 13:20

Just tested out code by torgosPizza from comment #13 - it works.

I like tcindie's idea of adding an item price column - that could simply be added at that same uc_cart_view_form loop.

Getting rid of tables, in general, is always a good idea ;)

#18

tcindie - November 8, 2009 - 22:10

Well, getting rid of tables isn't *always* a good idea.. they do still serve a purpose, and ubercart's usage of tables is justified more often than not. :)

 
 

Drupal is a registered trademark of Dries Buytaert.