b310 has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks,

I believe I have hit a brick wall. I am desperately trying to modify some code to allow me to place multiple entries of the same item in a cart. I've tried a few things and I'm not having much luck. At this point, I'm willing to pay someone for their services to help me fix this code.

Here's the deal..
I have two tables:
1.) catalog; fields - product_id(PK), description, price, category.
2.) item; fields - item_id(PK), prod_id(FK), color

A fellow monk was able to help me with code that created a color array so I can have a popup menu of colors per product appear. The code works beautifully! Well, I need to pass the item_id to the add_item subroutine not the product_id. (The item_id represents the variations of the product based on color). Well, I tried modifying the color array to bring along the item_id. When I run the script, my product appears as many times as there are colors for that item (if item has 3 colors - it appears 3 times). My change completely reversed what the color array intended to achieve.

I, then, proceeded to create a seperate array for item_id. Why? I have no idea. I've been staring at this for so long, I'm grasping at straws. Suffice it to say, the seperate array did nothing.

So, I'm coming back to Perl Monks in the hope of getting some real expert help not my amateur attempts.

I've pasted all the code involved for adding items. I hope someone can give me some help. Oh, I wasn't kidding about paying a fee, either.

Here's the code..
First, dispatch for proper action.
elsif ($choice eq "add") # add item to shopping cart { add_item ($dbh, $cart_ref, param ("product_id"), param("quantity") +, param("color")); $page .= format_cart_html ($cart_ref, 1); }

Second, displays the catalog of products.
sub get_product_table { my $sth = shift; my $dbh = WebDB::connect (); my ($clref, $ref); my @row; my @nav_link; my ($color, @color); while (my $ref = $sth->fetchrow_hashref ()) { ### courtesy code written by Perl Monk LAI #### $clref = $dbh->prepare ("SELECT color FROM item WHERE p +rod_id = ".$ref->{product_id}); $clref->execute(); while (my $color = $clref->fetchrow_hashref()) { push @{$ref->{color}}, $color->{color}; } my $serve_url = sprintf ("serve_image.cgi?product_id=%s;picture", + escape ($ref->{product_id})); # generate a form allowing a quantity of the item to be added # to the cart push (@row, start_form(-method=>'POST', -action=>url()), hidden( -name => "choice", -override => 1, -default => "add" ), hidden( -name => "product_id", -override => 1, -default => escapeHTML($ref->{product_id})), Tr ({-style=>"font-family: verdana; font-size: 10pt;"}, td (img ({-src => $serve_url, -alt => escapeHTML($ref->{product_id} +)})), td ({-width=>"80", -valign=>"top", -align => "center"}, escapeHTML( + $ref->{product_id} )), td ({-width=>"190", -valign=>"top"}, escapeHTML( $ref->{description +} ))), td (" "), Tr ({-style=>"font-family: verdana; font-size: 10pt;"}, td ({-colspan => "3", -align => "right", -style=>"font-weight: 700; + color:blue;"}, "Price: \$", escapeHTML( sprintf( "%.2f", $ref->{pri +ce}))), Tr ({-style=>"font-family: verdana; font-size: 10pt;"}, td ({-colspan => "3", -align => "right"}, "Colors:", popup_menu( -n +ame => "color", -values => $ref->{color}))), Tr ({-style=>"font-family: verdana; font-size: 10pt;"}, td ({-colspan => "3", -align => "right"}, "Quantity:", popup_menu( +-name => "quantity", -values => ["1", "2", "3", "4", "5", "6", "7", " +8", "9"])), td (image_button( -name => "Add Item", -src=>"../images/add.jpg", - +border=>"0" )))), Tr ({-valign=>"top"}, td ({-colspan=>"7", -width=>"100%"}, hr)), end_form() ); } $sth->finish (); return undef unless @row; # no items? return (table ({-border => 0, -align => "center", -style=>"font-fa +mily: verdana; font-size: 10pt;"}, @row)); }

Third, the add item subroutine.
sub add_item { my ($dbh, $cart_ref, $product_id, $qty, $color) = @_; # If the item isn't already in the cart, look it up from the datab +ase # and store it in the cart as a new entry with a quantity of zero. if (!exists ($cart_ref->{$product_id})) { my $sth = $dbh->prepare ("SELECT * FROM catalog_pet WHERE prod +uct_id = ?"); $sth->execute ($product_id); my $item_ref = $sth->fetchrow_hashref (); $sth->finish (); return if !defined ($item_ref); # this shouldn't happen... $cart_ref->{$product_id} = {}; # create new entry, indexed +by item ID $cart_ref->{$product_id}->{description} = $item_ref->{descript +ion}; $cart_ref->{$product_id}->{price} = $item_ref->{price}; $cart_ref->{$product_id}->{qty} = 1; } $cart_ref->{$product_id}->{qty} = $qty; $cart_ref->{$product_id}->{color} = $color; }

Finally, the view cart.
sub format_cart_html { my ($cart_ref, $show_links) = @_; my $total_price = 0; my @row; if (!keys (%{$cart_ref})) { return (p ("Shopping cart is empty.")); } $page .= h3 ({-align=>"center", -style=>"font-family: +verdana; font-size: 16pt; color: blue;"}, ("Shopping Cart")); push (@row, Tr ({-align => "CENTER", -valign=>"TOP", -BGCOLOR=>" +silver", -style=>"font-family: verdana; font-size: 10pt;"}, th ({-width=>"50"},("Item")), th ({-width=>"50"},("Qty")), th ({ +-width=>"90"},("Color")), th ({-width=>"190"},("Description")), th ({-width=>"94"},("Unit Price")), th ({-width=>"94"},("Price")) )); foreach my $product_id (sort (keys (%{$cart_ref}))) { my $item_ref = $cart_ref->{$product_id}; my $total_item_price = $item_ref->{qty} * $item_ref->{price}; $total_price += $total_item_price; # generate a link allowing the item to be deleted from the car +t my $url = sprintf ("%s?choice=delete;product_id=%s", url (), escape ($product_id)); push (@row, start_form(-method=>'GET', + -action=>url()), hidden( -name => "choice", -override => 1, -default => "update" ), hidden( -name => "product_id", -override => 1, -default => escapeHTML( + $product_id )), Tr ({-valign=>"center", -style=>"font-f +amily: verdana; font-size: 10pt;"}, td ({-align => "center"},(escapeHTML ($ +product_id))), td ({-align => "center"},( textfield( - +name => "quantity", -size => "1", -override => 1, -value => $item_ref +->{qty}))), td (escapeHTML ($item_ref->{color})), td (escapeHTML ($item_ref->{description})), td ({-align => "right"}, escapeHTML (sprintf ("%.2f", $item_ref->{price +}))), td ({-align => "right"}, escapeHTML (sprintf ("%.2f", $total_item_price +))), td (a ({-href => $url}, img ({-src => ".. +/images/delete1.jpg", -border => "0"}))), td (image_button( -name => "update", -src +=>"../images/update1.jpg", -border=>"0" )), end_form() )); } push (@row, Tr ({-align => "CENTER", -valign=>"center", -style=>" +font-family: verdana; font-size: 10pt;"}, td ({-colspan => "2"}, ""), td ({-colspan => "3", -align=>"right", -style=>"font-w +eight: 700;"}, "Total"), td ({-align => "right", -style=>"font-weight: 700;"}, escapeHTML (sprintf ("%.2f", $total_price))) )); return (table ({-align => "CENTER", -border => 0}, @row)); }

I hope someone is willing to give me some help.

Thanks!

Replies are listed 'Best First'.
Re: Duplicate items in cart do not appear
by chromatic (Archbishop) on Feb 23, 2003 at 21:52 UTC

    You've got several separate things going on here and you need to treat them separately. Putting them all together is obviously not working for you, and I can't read your code.

    I think you're asking several questions:

    • How do I put more than one of the same item in the cart at the same time?
    • How do I fetch all of the colors for a product out of the database without getting multiple entries for the product?
    • How do I hook those together for the user?

    The answer in the first case is to find or make something that uniquely identifies a product. That may be an item id, a product id, or a combination of one id and a color.

    The answer in the second case is either to keep a stateful loop or to do two separate queries. I'd probably write code such as this:

    sub get_colors_for_item { my ($dbh, item_id) = @_; my $sth = $dbh->prepare( 'SELECT color FROM item WHERE item_id = ? +' ); $sth->execute( $item_id ); my @colors; while( my ($color) = $sth->fetchrow_array()) { push @colors, $color; } return @colors; }

    The answer to your third question is to write much smaller subroutines. Separate the database work from the printing. What's important is to build and return a data structure that makes it easy to do the printing.

    I think you're getting caught up on a combinatorial explosion of details. Stop and take a deep breath. Then write some really small bits that do one simple thing at a time. Put them together -- not in one giant subroutine, but by calling them from a parent sub.

    It may be an artifact of posting here, but the indentation of this code is really hard to read. perltidy can help. By default, it does a really good job of making things much more readable. You might find that your code is more maintainable that way too.

      Hi.

      Thanks for your reply.

      My only issue is with question #1. I'm unable to figure out the write code that will allow a cart to have multiple entries of one item.

      I already have code that fetches all the colors for a specific product and displays it only once.

      That's where I'm running out of ideas.
Re: Duplicate items in cart do not appear
by jasonk (Parson) on Feb 23, 2003 at 22:52 UTC

    The last time you asked this question, I pointed out that you are storing the cart entries into a hashref called $cart_ref that is indexed by product_id, and because of this you can never have more than one item of any one product_id in the cart. You are still doing this, and thus you still cannot have more than one of any one product_id in the cart.

      Yes, I'm aware of that. What I need to do is store the entries into the cart indexed by item_id, which distinguishes a product from one color to another.

      When I try to bring in item_id with this statement of code:
      $clref = $dbh->prepare ("SELECT color FROM item WHERE prod_id = ".$ref +->{product_id}); $clref->execute(); while (my $color = $clref->fetchrow_hashref()) { push @{$ref->{color}}, $color->{color}; }
      And, then carry everything over to my Add Item subroutine, I receive Shopping Cart is Empty message.

      Now, here is what I did to the code to try to bring in the item_id and which later produces an empty shopping cart message.
      $clref = $dbh->prepare ("SELECT item_id, color FROM item WHERE prod_id + = ".$ref->{product_id}); $clref->execute(); while (my $color, $item_id = $clref->fetchrow_hashref() +) { push @{$ref->{color}, {$ref->{item_id}}, $color->{color +}, $item_id->{item_id}; }

      I'm trying to create a $ref->{item_id} so it is the same as all the other variables. Then, I'm trying to pass the $ref->{item_id} to the add_item subroutine.

      It is quite apparent that I'm goofing up somewhere. I just don't know where.
      Here's also what I've done to the add_item subroutine...
      sub add_item { my ($dbh, $cart_ref, $item_id, $qty, $color) = @_; # If the item isn't already in the cart, look it up from the datab +ase # and store it in the cart as a new entry with a quantity of zero. if (!exists ($cart_ref->{$item_id})) { my $sth = $dbh->prepare ("SELECT * FROM item, catalog_pet WHER +E catalog_pet.product_id = item.prod_id AND catalog_pet.product_id = +?"); $sth->execute ($item_id); my $item_ref = $sth->fetchrow_hashref (); $sth->finish (); return if !defined ($item_ref); # this shouldn't happen... $cart_ref->{$item_id} = {}; # create new entry, indexed by +item ID $cart_ref->{$item_id}->{description} = $item_ref->{description +}; $cart_ref->{$item_id}->{price} = $item_ref->{price}; $cart_ref->{$item_id}->{qty} = 1; } $cart_ref->{$item_id}->{qty} = $qty; $cart_ref->{$item_id}->{color} = $color; }

      You can see that I'm trying to store the cart entries by item_id. This attempt of coding produces an empty shopping cart message.
        Your code:
        $clref = $dbh->prepare ("SELECT item_id, color FROM item WHERE prod_id + = ".$ref->{product_id}); $clref->execute(); while (my $color, $item_id = $clref->fetchrow_hashref() ) { push @{$ref->{color}, {$ref->{item_id}}, $color->{color}, $ite +m_id->{item_id}; }
        has a number of problems. First, you really should always use bind variables with DBI. If you don't, you'll have to escape the variables you put into the SQL yourself in order to prevent big security holes.

        Next, while (my $color, $item_id = $clref->fetchrow_hashref() does not make $item_id a lexical ("my") variable. It's a global. If you had use strict and warnings on, it would tell you about that. Also, it's not assigning what you want to $color or $item_id. You're just fetching the hashref that represents a row into $color. I can't really tell what you were trying to do with this code.

        Your add_item sub needs to use product_id and color in the SQL statement. Otherwise you will get multiple rows back. It's still doing a "select *" too.

Re: Duplicate items in cart do not appear
by perrin (Chancellor) on Feb 24, 2003 at 18:18 UTC
    Now that you're talking about spending money, I really think you should consider using an existing open source package to build your store. Writing it all from scratch is a good learning exercise, but not a good plan when your business and your money are involved, and frankly you're still too much of a newbie at Perl to write a complex commerce system without help. Take a look at Interchange, for a good system that you can build on.
      Hi.

      A fellow monk was able to help me with my problem.

      By going through all this, I completely understand the difference between arrays and hashes. That was my primary goal in this whole exercise was to learn. Find other types of modifications and see what I can do.

      My problem is fixed and I'm a happy camper.