Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Ruby

Conditionally change css

I'm pretty new to javascript, and I hacked this solution together mostly with copy/paste. However, it's not perfect.

The main thrust of my question is that I want to change a CSS attribute only if it needs changing.

Here's the main view: words/index.html.erb:

<div class="col-md-10">
      <%= render('layouts/all_categories') %>
      <%= render partial: 'words_grid', locals: { words: @words } %>
</div>

In the CSS, the words_grid (which has an id of 'words_grid') has display: none; set.

When the 'Show All' link is clicked here:

<table class="table" id="all_categories">
  <thead>
    <tr>
      <th>
        Word <%= link_to "Show All", all_words_path, class: "btn btn-sm btn-info", id: "show-all-button", remote: true %>
      </th>
      <th>Definition</th>
      <th>Categories</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% current_user.word_list.words.alphabetical_order_asc.each do |word| %>
      <tr>
        <td>
          <%= link_to edit_word_path(word) do %>
            <%= word.title %> <span class="glyphicon glyphicon-pencil"></span>
          <% end %>
        </td>
        <td><%= simple_format(word.description) %></td>
        <td class="categories_container">
          <% word.categories.alphabetical_order_asc.each do |category| %>
            <%= link_to category.title, fetch_words_path(category_id: category.id), remote: true, class: "btn btn-info btn-sm", role: "button" %>
          <% end %>
        </td>
        <td>
          <%= link_to word, method: :delete, data: { confirm: 'Are you sure?' } do %>
            <span class="glyphicon glyphicon-remove"></span>
          <% end %>
        </td>
      </tr>
    <% end %>
  </tbody>
</table>

... the 'all_words' method is run in the words_controller:

def all_words
  respond_to do |format|
    format.js
  end
end

This runs the all_words.js :

$("#all_categories").css({'display':'block'});
$("#words_grid").css({'display':'none'});

Now, two questions:

  1. Is there a simpler way to do this? Is my code correct? It does work, but that doesn't mean it's good.

  2. I want the javascript to effectively do this: Step 1: IF #allcategories display is presently set to 'none', change it to 'block'. Step 2: IF #words_grid's display is presently set to 'block', change it to 'none'.

How do I this?

4 Answers

Tim Knight
Tim Knight
28,888 Points

Hi Andrew, if I'm reading this right it seems what you have is more of a JavaScript problem than a Ruby/Rails problem. Here's what I mean... I think trying to run this as a remote requested file and having a method that just returns your JavaScript is what's making it really complex.

Instead of using the Rails link_to to create your "Show All" button, I'd just do something like this:

<a href="#" class="btn btn-sm btn-info" id="show-all-button">Show All</a>

Remove the the entire all_words method and it's route. Then manage your display behavior in jQuery. It's hard for me to tell from your example what you're code is trying to show, but I'd just creating something like...

$('#show-all-button').on('click', function() {
  // Show the fields that you want.
});

Now if I'm not seeing this right and you're doing an AJAX call to receive the content I'd say that even David Heinemeier Hansson (the creator of Rails) suggests using jQuery's $.get() method than using Rails' complex remote.

Does that help? Basically I'd try to pull out some of the Rails UI complexity and push it to your jQuery.

Thanks Tim. I must admit I'm a little confused about how best to approach this. I would direct you to the website, but I don't want to make the link public yet, because I don't have all the various permissions setup yet - someone malicious could mess things up.

I'll try to shed some light on this part of the app:

On the page is a table, displaying list of the User's Wordlist's words.

The list of words is displayed in a partial. Currently, there are two partials in the view:

Partial 1 ("all_words"): contains all words. When the user lands on the page, this partial has display: block.

Partial 2 ("from_category"): contains words of a selected category. When the user lands on the page, this partial has display: none.

Words have Categories. A user can click on a Word's Category and, through ajax, Partial 2 changes to display only those words that have that category and, simultaneously, Partial 1's 'display' is set to 'none' and Partial 2's 'display' is set to 'block'.

Therefore, when a User lands on the page and then clicks on a Category, they are presented with a list of words that have that category, without a page refresh.

Now, the problem is, how to get BACK to showing all the words.

I'm sure this should be very simple. I have a "Show All" link just above the table. I want the User to be able to click that and the table to display all Words.

I expect this is a simple javascript, but this is all quite new to me, so I need some hand-holding :-)

I want the javascript itself to be conditional, so it doesn't run unnecessarily i.e. when the page is ALREADY displaying all words, when a User clicks on "Show All" I want nothing to happen, no server requests or anything, so no system resources are used unnecessarily and everything runs quickly.

Tim Knight
Tim Knight
28,888 Points

Andrew the thing to remember though is that by using something like I did with on('click') your code is only going to run when the click happens. The rest of showing and hiding information you seem to have a handle on with the .css method in jQuery. So it seems to me if you're wanting to get back to something like showing all of the words you could just hide your #from_category and show your #all_words right?

$(function () {
  $('#show-all-button').on('click', function() {
    $('#from_category').css('display':'none');
    $('#all_words').show('display':'block');
  });
});

I'd consider checking out the AJAX course too http://teamtreehouse.com/library/ajax-basics just to give you a handle on how jQuery could interact with pulling that data down. What you'd likely then be doing is using Rails' API to grab the database as JSON through jQuery.

Hi Tim, thanks for this, but I'm having trouble getting it to work, which is strange. I'll hack away a bit longer and get back to you.

Tim Knight
Tim Knight
28,888 Points

One thing I would suggest too is running the jQuery commands right from your browser's inspector so you can get an idea that the page is processing the execution.

Nah, getting stumped here.

index.html.erb view:

<div class="col-md-10">
  <%= render('layouts/all_words') %>
  <%= render partial: 'words_grid', locals: { words: @words } %>
</div> 

(I've changed the id of one of the partials and the 'show all' link)

pages.css:

#all_words {
    display: block;
}

_all_words.html.erb:

<table class="table" id="all_words">
  <thead>
    <tr>
      <th>
        Word <button class="btn btn-sm btn-info", id="show-all-words-button">Show All</button>
      </th>
      <th>Definition</th>
      <th>Categories</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% current_user.word_list.words.alphabetical_order_asc.each do |word| %>
      <tr>
        <td>
          <%= link_to edit_word_path(word) do %>
            <%= word.title %> <span class="glyphicon glyphicon-pencil"></span>
          <% end %>
        </td>
        <td><%= simple_format(word.description) %></td>
        <td class="categories_container">
          <% word.categories.alphabetical_order_asc.each do |category| %>
            <%= link_to category.title, fetch_words_path(category_id: category.id), remote: true, class: "btn btn-info btn-sm", role: "button" %>
          <% end %>
        </td>
        <td>
          <%= link_to word, method: :delete, data: { confirm: 'Are you sure?' } do %>
            <span class="glyphicon glyphicon-remove"></span>
          <% end %>
        </td>
      </tr>
    <% end %>
  </tbody>
</table>

_words_grid.html.erb:

<div id="words_grid" class="table" >
</div>

words_controller:

def from_category
    @selected = current_user.word_list.words.joins(:categories).where( categories: {id: (params[:category_id])} )
    respond_to do |format|
      format.js
    end
end

from_category.js.erb:

$("#all_words").css({'display':'none'});
$("#words_grid").html("<%= escape_javascript(render partial: 'words_list', locals: { words: @selected } ) %>");
$("#words_grid").css({'display':'block'});

views/words/show_all_words.js.erb:

$( "#show-all-words-button" ).click(function() {
    ("#all_words").css({'display':'block'});
    ("#words_grid").css({'display':'none'});
});

When I click on a Word's Category, everything happens as expected: the #all_words div is hidden, the #words_grid is shown.)

This is noteworthy I think - when this happens, style attributes appear in the #all_words html and the #words_grid html (when looking at the Chrome Inspector), with 'display: none;' and 'display: block;' respectively.

Then, when I click the 'Show All' button, nothing happens. At all! The html doesn't change, not even the webbrick server log shows anything, and I have no idea why.

Tim Knight
Tim Knight
28,888 Points

You have a comma in your button tag that shouldn't be there. Could that be through off your click event?

The jquery event wasn't bound to the button. I moved the javascript into application.js and that solved the binding issue, but I'd like to know why.

Tim Knight
Tim Knight
28,888 Points

You were likely not executing the JavaScript on DOM load which is what happens when you support the code with:

$(function () {
...
});

from my exactly. I'm sure your application.js probably had something already to check if the DOM was loaded which than bound the event to the button.

I don't quite understand that, but anyway - the main problem I have now is that when the table partials change, the table contents width don't span the width of the table div, leaving a big white empty space on its right side. I don't know why - before all this it wasn't a problem. This is some really deep technical problem I think that I'll be lucky to get to the bottom of anytime soon.

If your curious, this branch is now on my github: https://github.com/Yorkshireman/mywordlist/tree/onclick

Obviously I'd appreciate any help, but it's a tough one. I don't want to post the heroku link here, but feel free to pull the repo and run it locally of course.

Tim Knight
Tim Knight
28,888 Points

I would suggest reading more about the DOM ready at http://learn.jquery.com/using-jquery-core/document-ready/.

As for your width issue I'd just inspect the CSS and see if the show/hide that you're doing isn't messing with the document flow. You might need to specify the width specifically within your CSS. Best of luck with you project.