Practical 6

0. Finish Practical Sheet 5.

(Alternatively, move your old depot application out of the way and get a fresh copy by first creating a fork of https://github.com/cs424/depot.git on your github page, and secondly cloning the fork into your working directory. Then cd depot and run rake db:migrate and rake db:seed to set up the database.)

1. Partials. A partial is a named chunk of a view template in its own file. With the render method, it can be included into views, layouts, or even other partials. And you can pass arguments to a partial, … it really has a lot in common with a subroutine of a computer program.

The filename of a partial starts with an underscore (_), followed by the name of the partial and the extension .html.erb.

Let’s start by moving the formatting of line items in the shopping cart into its own file. This is a sufficiently complex task too deserve a place of its own. In the app/views/line_items folder, create a file _line_item.html.erb. Note the leading _ and the use of singular in the file name. This file now contains a partial with name line_item (without leading _). Copy the code that describes the table row of a line item in a shopping cart from the cart view into this new file:

<tr>
  <td><%= item.quantity %>&times;</td>
  <td><%= item.product.title %></td>
  <td class="item_price"><%= number_to_currency(item.total_price) %></td>
</tr>

Then replace each occurence of item by line_item (3 times) so that it matches the name of the partial.

2. Now replace the loop over all line items which contains that chunk in the cart view by the single line:

<%= render @cart.line_items %>

Reload the page and make sure it still works as desired. (Note the = sign in the opening ERB bracket <%=.) If it works, the render method has correctly identified the thing to render as a list of line items, has found the instructions for rendering a single line item in the line_item partial in the line_items view folder and has applied this to each item in the list. Note how a series of naming conventions makes it possible to remove an entire level of complexity from the program.

3. Next, we are going to wrap up the shopping cart display into a partial, so that we can easily include it in the side bar of the catalog page.

Following the same naming conventions as before, create a new file for a partial cart (which name? which folder?). Then copy the contents of the cart view show.html.erb into the new file and replace each occurrence of @cart by cart so that it matches the name of the partial. Also remove the notice at the top of the file.

You can now replace everything except for that notice in the original cart view file by the single line:

<%= render @cart %>

Check whether it still works as desired.

3a. Actually, what do the tests report? If there are errors, what’s needed to fix them? If not, this might be a good time to commit the recent changes.

4. You are now in a position to display a cart wherever you want, with a single command. In order to have a cart to display on the store page, add the lines

include CurrentCart
before_action :set_cart

before the index method of the store controller (which file?). This will take care of setting @cart to the current cart when the index action is called, and make the variable available in the view.

5. The partial can now be invoked by the text

<% if @cart %>
  <div id="cart" class="carts">
    <%= render @cart %>
  </div>
<% end %>

inside the <nav> element of the layout (which file?). Try it out!

6. The class attribute "carts" of the <div> element that contains the cart makes it pick up the style defined in the stylefile carts.scss. In order to make the cart more readable when it is displayed in the green sidebar (where it has the id attribute cart), add

#cart {
  h2 {
    margin-top: 0;
  }
  background: white;
  border-radius: 0.5em;
  margin: 1em;
  padding: 1.414em;
  @media (min-width: 30em) {
    margin: 0; // desktop doesn't need this margin
  }
}

to the nav section of the application.scss style file.

7. Now adding a line item should redisplay the store and not the cart. Change the appropriate line in the create method of the line items controller to

format.html { redirect_to store_url }

8. Run the tests again and see what fails. If all works fine, commit the changes to your local git repository, and push them to the github cloud.

git add .
git commit -m "include cart in store layout"
git push

9. Ajax. If only the contents of the cart has changed then only that portion of the page really needs to be updated, rather than requesting, composing and sending a new version of the entire page. Ajax refers to the ability of the browser to change the internal (DOM) representation of a page, and to interact with the server on that level. This usually involves a few (small) changes to the existing programs. First we instruct the "Add to Cart" button to send an Ajax request to the server (rather than requesting a new copy of the entire page. This is done by adding the option remote: true to the button (which file?):

<%= button_to 'Add to Cart', line_items_path(product_id: product),
  remote: true %>

10. On the receiving end, we need to allow the create method of the line items controller to respond to Ajax requests. This is done by adding one line

format.js

to the create method of the line items controller, after the line you changed in step 8.

11. Finally, in the line items views folder, create a file create.js.erb with the following two lines of code:

cart = document.getElementById("cart")
cart.innerHTML = "<%= j render(@cart) %>"

Executing this code will replace the content of the <div> with ID cart by the result of rendering the cart partial.

rails knows how to translate this into proper JavaScript; ruby templates are treated in the same was as they are in other .erb files; and j is short for escape_javascript, a helper method that converts from ruby syntax into JavaScript syntax.

Try it out: the page should refresh much quicker and smoother!

11a. If all works fine, commit the changes to your local git repository, and push them to the github cloud.

12. Highlights. CSS can do way more with your pages. We can use the animation feature to highlight the latest change to the cart. For this, add the following

@keyframes line-item-highlight {
  0% {
    background: #8f8;
  }
  100% {
    background: none;
  }
}

.line-item-highlight {
  animation: line-item-highlight 1s;
}

to the stylesheet line_items.scss.

13. In order to highlight the latest item in a cart, we need a way to identify it. For that, replace format.js in the line item controller’s create method by:

format.js { @current_item = @line_item }

Then, in the line_item partial, replace the opening <tr> by:

<% if line_item == @current_item %>
<tr class="line-item-highlight">
<% else %>
<tr>
<% end %>

I don’t particularly like this code. (Why? Because there must be a cleaner way to achieve this with the content_tag helper. If you find one, there is a comment box below …) It marks (only) the latest item with the "line_item_highlight" class.

Now add a new item a watch Yellow Fade in action.

14. Empty Carts. If the cart is empty, there is no need to display it. One way to achieve this is by first defining a general helper method that conditionally renders objects. Add

def render_if(condition, object)
  if condition
    render object
  end
end

to the ApplicationHelper module in the app/helpers folder. Then replace the render command in the <div> with ID "cart" in the application layout by

          <%= render_if @cart && @cart.line_items.any?, @cart %>

This will render @cart only if it exists, and if it contains at least one item.

15. Remove the message about currently empty carts (from where?). It is no longer needed, and it no longer works correctly.

16. Testing AJAX. To test the AJAX functionality, add the following test to the line items controller test.

test "should create line item via ajax" do
  assert_difference('LineItem.count') do
    post line_items_url, params: { product_id: products(:one).id },
         xhr: true
  end
  assert_response :success
end

Now run the tests.

16a. If all works fine, commit the changes to your local git repository, and push them to the github cloud.

17. Formulate a question about the new concepts in the comment box below.

Written on November 18, 2020 by CS424.