I still get the flicker as if the tr element is showing with the code suggestion below if I use grow as hinted. I think blind_down may be hiding the fact that the code is buggy. Hiding the tr element and using grows shows the same flicker that was fixed earlier in the chapter by hiding the div. I’m assuming the bug isn’t related to these js effects not working with block elements (why would the blind_down work with showing the cart the first time???) So, why is the flicker still occurring with the below code then? Seems like the code suggestion below is still buggy.

I had trouble finding a canonical mapping of the effects as they are named in the effects.js => how to access them in RoR?. I tried “shake” and “grow” but gave up.

Marcello:

You should use a symbol with the name of the effect (as set in the Effect.VIsualEffect? js functions). Something like that:

page[:current_item].visual_effect :grow 

Any thoughts on sharing the cart item partial between the AJAX & the initial page display?

I came here looking for an answer to this problem, thought i’d find it here?! When I just add the line page[:current_item].visual_effect :grow to add_to_cart.rjs, the cart just blinks and winks at me, but its not pretty.

  • where does the page[:current_item].visual_effect :grow line go?
  • how do you hide the item initially if qty is 1?
  • whats the answer to the shared cart partial question?

I’m not sure about the cart item question, but here’s my guess. If you want special effects based on the cart, you could have the cart items initially be hidden using CSS (such as using the hidden_div trick). However, only the Javascript from add_to_cart.rjs would be able to “unhide” the item by using “grow” or similar.

This makes it tough to share the code because the initial page display doesn’t have the code to unhide the divs. If the user does a refresh, their cart is the same, but the store controller doesn’t know to unhide the items.

I wanted to use the BlindDown? effect for this if a new item appeared and the Highlight if the item already exists. These two effects need these lines in my add_to_cart.rjs:


page[:current_item].visual_effect :blind_down if @current_item.quantity == 1

page[:current_item].visual_effect :highlight,
                                  :startcolor => "#88ff88",
                                  :endcolor => "#114411" unless @current_item.quantity == 1
For hiding the line I wrote a new helper, just like the one for divs, named hidden_tr_if… this goes to store_helper.rb:

def hidden_tr_if(condition, attributes = {})
  if condition
    attributes["style"] = "display: none" 
  end
  attrs = tag_options(attributes.stringify_keys)
  "<tr #{attrs}>" 
end

Now the _cart_item.rhtml has to use this helper. One question is: what happens if we don’t have JavaScript?? The CSS would still kick in and hide the tr element… but we can get around this by choosing the condition right. This is how I call the method in my _cart_item.rhtml:


<% if cart_item == @current_item %>
    <%= hidden_tr_if(cart_item.quantity == 1 && request.xhr?, :id => "current_item") %>
<% else %>
   <!--  ... -->

Now I only hide the line using CSS if the view got invoked from a xhr request and this request can only happen if JavaScript? is active.

There is only one problem I still have and right now I don’t have the solution: My BlindDown? is not beautiful. It is way too fast. I tried to change this line in add_to_cart.rjs


page[:current_item].visual_effect :blind_down if @current_item.quantity == 1

to this


page[:current_item].visual_effect :blind_down,
                                  :duration => 10 if @current_item.quantity == 1

but it has no effect on the speed at all. I tried all kinds of values without effect. I also tried the same duration parameter on the Highlight effect, there it works like a charm.

Anybody knows what is wrong with my BlindDown??

I’m new to Wiki’s so sorry if this is in the wrong place but…

In answer to the question above “Anybody knows what is wrong with my BlindDown??”

I had a look at http://script.aculo.us and it says: “Works safely with most Block Elements, except table rows, table bodies and table heads.”

This could be the problem if you are using table rows?

Just a note following the last comment to keep your code clean.

Instead of adding a new method to the store_helper.rb file, replace the hidden_div_if method with:


def hidden_element_if(element, condition, attributes = {})
  if condition
    attributes["style"] = "display:none" 
  end
  attrs = tag_options(attributes.stringify_keys)
  "<#{element} #{attrs}>" 
end

Then in _cart_item.rhtml, call the method with:


<% if cart_item == @current_item %>
  <%= hidden_element_if("tr", cart_item.quantity == 1 && request.xhr?, :id => "current_item") %>
<% else %>
...

Just make sure you change the method call accordingly for the div tag in store.rhtml.

I don’t know if it’s a good idea, but I’ve done the following

module StoreHelper
  def method_missing(name, *params, &block)
    if match = name.to_s.match('hidden_(\w+)_if')
      hidden_if(match[1], *params, &block)
    end
  end

  def hidden_if(tag, condition, attributes = {}, &block)
    if condition
      attributes['style'] = 'display: none;'
    end
    attrs = tag_options(attributes.stringify_keys)

    if block
      concat("<#{tag}#{attrs}>", block.binding)
      yield
      concat("</#{tag}>", block.binding)
    else
      "<#{tag}#{attrs}>" 
    end
  end
end
And I use the following on the templates.

<%= hidden_tr_if(@current_item.quantity == 1, :id => 'current-item') %>
  ...
</tr>
<% hidden_div_if(@cart.items.empty?, :id => 'cart') do -%>
  <%= render :partial => 'cart', :object => @cart %>
<% end -%>

This way I can use both idioms and for any tag.

Nathan Manzi says:

I modified the hidden_div_if function to help accomplish hiding/revealing individual cart items. I renamed the function to hidden_element_if, adding in an extra parameter for specifying an element to be hidden (as the cart items are wrapped in td tags) and introduced block-handling into it which makes the code a bit cleaner by automatically ending the tag (removing the obscure s):


def hidden_element_if(condition, element, attributes = {}, &block)
  if condition
    attributes["style"] = "display:none" 
  end
  attrs = tag_options(attributes.stringify_keys)
  content = capture(&block)
  concat("<#{element} #{attrs}>")
  concat(content)
  concat("</#{element}>")
end

Anthony Ettinger says:

I tried effects too, but also saw that they only work on block elements. Since the shopping cart is a table (as it should be), there’s no point in working around the clock to get a shake/grow pair working. I originally thought a slide_right/left would be cool for cart items, but alas, they only work on blocks as well.

Its more important to have the data show in a table (qty | title | price) then it is to convert to a pseudo-table using nested unordered lists…although that would be the route I would take should it be a requirement.

Jinyoung says:

My conclusion: 1. The blind/slid down effect doesn’t work well with a table row. However the grow effect works with a table row. Of course, it’s not prefect. the effects works well with firefox 3.0.3 in xUbuntu 8.10(beta), nvidia graphic card machine. And the grow effect works well but table row expanding effect still flickers with IE 6.0.2 in Windows XP, nvidia graphic card machine(a different machine).

2. To tell the truth, I can’t understand the intent not meaning of a question that “Does this make it problematic to share the cart item partial between the AJAX code and the initial page display?”

3. Anyway.. below is my code.

In _cart_item.html.erb

<% if cart_item == @current_item %>
  <tr id="current_item" style="display:hidden">
<% else %>
  <tr>
<% end %>
    <td><%= cart_item.quantity %>×</td>
    <td><%=h cart_item.title %></td>
    <td class="item-price"><%= number_to_currency(cart_item.price) %></td>
  </tr>
In add_to_cart.js.rjs

page.replace_html("cart" , :partial => "cart" , :object => @cart)
page[:cart].visual_effect :blind_down if @cart.total_items == 1
page[:current_item].visual_effect :grow if @current_item.quantity == 1
page[:current_item].show if @current_item.quantity > 1
page[:current_item].visual_effect :highlight, :startcolor => "#88ff88" , :endcolor => "#114411" 

Johnnysocko says:

What the guide asks for leaves a little to be desired. I’m had some interpretation issues with what he was asking for. I suppose using the author’s lead for creating the cart as being hidden initially and then using the blinddown effect to expose it was somehow applicable to the problem he’s encouraging you to solve.

Getting the thing to just grow by adding a visual_effect = grow for quantity 1 was simple enough. It’s originally hidden by definition since it doesn’t exist for quantity = 0, is it not? I didn’t really get the whole hidden thing. Grow by itself for me renderred pretty strange in IE, bleeding in from the main page till it was in final position.

In the end I settled on trying the “parallel_effect” since that sounded pretty cool. But, after several attempts, I couldn’t figure out how to integrate that into the add_to_cart.js.rjs file. So, I left the highlight visual effect intact, then went this route, as ugly as it may be for current_item.quantity = 1.

First, I hid the cart_item display for quantity 1.


<% if cart_item == @current_item && @current_item.quantity == 1 %>
  <tr id="current_item" style="display:none">
<% elsif cart_item == @current_item %>
  <tr id="current_item">

After the rendering of the cart_item partial in _cart.html.erb, I added this gem:


<% if !@current_item.nil? && @current_item.quantity == 1 %>
  <%= render(:partial => 'parallel') %>
<%end %>

Then, I brute forced the parallel effect in a new partial, _parallel.html.erb:



Then, I could easily play around with combinations of Appear, Blinddown, Grow. Behavior was inconsistent between IE and Firefox3. How about the author stop by and give his answer?

Groxx says:

My changes are as follows (I have no non-ajax degradation, I may add it later). I like the combination they create, and the slide-down effect has a neat bug/feature when tables are involved.

Very short video of effects: http://screencast.com/t/V1fD77D4 Note how the “Your Cart” rolls down a moment after the table is revealed :)

To store_controller.rb:

  def add_to_cart 
    product = Product.find(params[:id]) 
    @cart = find_cart 
    @current_item = @cart.add_product(product) 
    respond_to {|format| format.js}
  rescue ActiveRecord::RecordNotFound 
    logger.error("Attempt to access invalid product #{params[:id]}") 
    flash[:notice] = "Invalid product" 
    redirect_to :action => 'index' 
  end

  def empty_cart 
    session[:cart] = nil 
    respond_to {|format| format.js}
  end 
To add_to_cart.js.rjs: (specifically, :slide_down)

page.replace_html("cart", :partial => "cart", :object => @cart) 
page[:cart].visual_effect :slide_down if @cart.total_items == 1
page[:current_item].visual_effect :highlight, 
                                  :startcolor => "#88ff88", 
                                  :endcolor => "#114411" 
Added empty_cart.js.rjs:

page[:cart].visual_effect :drop_out
And finally to layouts/store.html.erb:

<div id="cart" style="display: none"><!-- AJAX fills here --></div>
Which nicely solves the entire display problem, though effectively requires AJAX to replace the display properties / content.

As to the people getting nil errors, it’s likely because you’re trying to check ‘cart.items.empty’ or something similar. The problem is coming from the fact that ‘cart’ itself is nil, so it has no ’.items’ method (it’s actually a nil-class object at this point, not a cart object). Check with ‘cart.nil? or cart.items.empty’ to handle both cases.

Leandro says:

I didn’t understand the question about sharing code between partial and index, but my code seens to work just fine and it’s fairly simple.

Added two lines to handle the insertion of a new product at the cart and inserted a condition to the “yellow fade” effect so it affects only existing product at the cart.

add_to_cart.js.rjs

page[:current_item].hide if @current_item.quantity == 1
page[:current_item].visual_effect :grow if @current_item.quantity == 1

page[:current_item].visual_effect :highlight, 
                                  :startcolor => "#88ff88", 
                                  :endcolor => "#114411" if @current_item.quantity > 1

Alexey says:

Here is what I did.

First of all, looks like :blind_down effect isn’t working well when it comes to table rows. We can apply :grow effect instead:

add_to_cart.js.rjs

page[:current_item].visual_effect :highlight,
                                :startcolor => "#88ff88" ,
                                :endcolor => "#114411" unless @current_item.quantity == 1

page[:current_item].visual_effect :grow if @current_item.quantity == 1

But now we’ve got a little nuisance: “current_item” renders first, and after that our AJAX redraws it. Therefore we see slight blinking. I’ve come with this bit of an ugly coding to avoid this:

_cart_item.html.erb

<% if cart_item == @current_item %>

  <% if @current_item.quantity == 1 %>
      <tr id="current_item" style="display: none">
    <% else %>
      <tr id="current_item">
    <% end %>

<% else %>
  <tr>
<% end %>

Any suggestions?

h4. WT I DRYed the hidden_div_if:
module StoreHelper
  def hidden_x_if(x, condition, attributes = {}, &block)
    if condition
      attributes["style"] = "display: none" 
    end
    content_tag x, attributes, &block    
  end

  def hidden_div_if(condition, attributes = {}, &block)
    hidden_x_if "div", condition, attributes, &block
  end
end
Then I replaced the tag with that in _cart_item:
<%
# this partial lets the item slide in if it isn't in the cart yet
attributes = {}
if cart_item == @current_item
  attributes[:id] = 'current_item'
end
%>

<% hidden_x_if "tr", attributes[:id] && @current_item.quantity == 1, attributes do %>
  <td><%=h cart_item.quantity %></td>
  <td><%=h cart_item.title %></td>
  <td class='item-price'><%=h number_to_currency cart_item.price %></td>
<% end %>

“grow” worked for me (thanks guys) and I didn’t worry about the hidden div without javascript since @current_item is only set for the xhr branch of the add_to_cart for now.