Task D: A Dash Of Ajax #3

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 </element>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 %>&times;</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:

<script type="text/javascript">
  new Effect.Parallel([
      new Effect.Appear('current_item', { sync: true }),
      new Effect.BlindDown('current_item', { sync: true })], { duration: 5 });
</script>

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?

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License