0

In my app I have user notifications where the user gets notified for certain actions. These notifications are being shown in a dropdown from the navbar (like Facebook or S.O.). All of the notifications have a boolean attribute called :read and are default set to false. I'm using bootstrap with the dropdowns (in case that helps).
I want to create a method where when the user clicks to open the dropdown, all of their unread notifications become read.

Here is what I have so far for the method.

def read_notifications
  PublicActivity::Activity.where(recipient_id: current_user).where(read: false).update_all(:read => true)
end

This updates all of the current user's notifications to :read => true when the method is called. In the view here is what I had so far for the dropdown link.

<%= link_to read_notifications_path, :class => "dropdown-toggle notifications_icon", :'data-toggle' => "dropdown", :controller => "application", :action => "read_notifications", :method => :post do %><% end %>

and the routes.rb I had this.

match "/read" => "application#read_notifications", :as => "read_notifications", via: 'post'

Now I know what I have is wrong, but even so when I click the link it does switch all of the user's notifications to read, it just acts also as a link (duh) and goes to a different page.
As you know, the link on a bootstrap dropdown is "#".

Does anyone know how I can set this up properly where when the user clicks the notification link in the navbar, ALL it does is open the dropdown and change the boolean value to true for all notifications.

I know this is possible, I just haven't been able to figure it out yet.
Thanks for taking a look at it.

EDIT

JS file

$(".notifications_icon").on("click", function(){
  $.post("/read", function(data){
    $('.notification_badge').text("");
  });
});

View

<%= link_to "#", :class => "dropdown-toggle notifications_icon", :'data-toggle' => "dropdown" do %>
  <span class="notification_badge"><%= find_unread_notifications_count(current_user) %></span>
<% end %>

This is Posting to the /read to read all of the notifications but it's not updating the count

2 Answers 2

1

You want a dash of unobtrusive JS. For example, SO has a class js-inbox-button that, when clicked, triggers updates on unread counts (both client and server). I won't dig into their JS source, but it's fairly simple to build.

You seem to already have a relevant class (notifications_icon), though you might want to use something else. When the link is clicked, use jquery $.post.

$(".notifications_icon").on("click", function(){
  $.post("/read", function(data){
    // remove unread count
    $('.notification_badge').text('');
  });
});

Now this is a very basic implementation. Couple of suggestions:

  1. Only make requests when necessary (check for unread count on page first)
  2. Use a data attribute on the link to pass /read path. That way you can still use your path helpers instead of hardcoding a path.
  3. Store the above JS in a separate file (unobtrusive)
Sign up to request clarification or add additional context in comments.

10 Comments

@Justin I'd advise against the other answer. For one, that will make a request every time you click, and it re-renders a partial. If a user has JS disabled, it will go the /read route when they click. It is obtrusive and relies on an inflexible way to manage JS callbacks (with erb.js files). This is even noted in the guides: edgeguides.rubyonrails.org/…. You have much more control if you extract the handler instead of the easy "remote: true" fix.
I am trying to do it your way @Damien, but I'll need a bit more help if that's okay. My weakness is JS. So far, everything you've said works great, but I haven't added anything to the // handle response part because I'm unsure what goes there. Without that, just by clicking the link it does trigger the method but the count wont update (obviously because I haven't added the code yet) until I reload the page. Could you possibly point me in the right direction for the code that goes inside the data function. Again, thanks so much already for the help.
@Justin no problem. What is the code like surrounding the count? Is the count wrapped in a particular div?
Right, what you probably want to do is remove the notification badge entirely. You can place the following code in the response block in JS: $('.notification_badge').remove(); I'll update my answer. Also, be aware that this will remove all divs with notification_badge class so be sure to scope if necessary, like .myinboxnav .notification_badge. This also depends if/how you update the count when a new unread msg comes through. You could, instead of removing the div, only remove the content: $('.notification_badge').text('')
@Justin that tells me possibly the response block is not running. Try replace with alert('hello'); to be sure. If so, that is likely because you are not returning a 'successful' response. Within your controller method, after you update the messages, try: render nothing: true, status: 200, or on Rails 4: head :ok.
|
1

AJAX.

By adding remote: true you're starting with AJAX. Now the call goes to your path, and nothing happens! yay!

You want something to happen, though. So in your controller (I wouldn't do it in the application_controller, if I were you... activities_controller.rb maybe?):

controllers/activities_controller.rb

def read_notifications
  PublicActivity::Activity.where(recipient_id: current_user).where(read: false).update_all(:read => true)
  respond_to do |format|
    format.js
  end
end

You're on your way to Asynchronous loading! Now you need a js file to match it. So, since you've already moved this action to the activites, in your view, you'll have a new js.erb file to create (notice that it's in the same folder in the views as the controller name, and the file is named after the action):

views/activities/read_notifications.js.erb

$('#your_menu_div').html("<%= j render partial: 'activities/notifications' %>");

Now you create a partial (views/activities/_notifications.html.erb) that gets rendered into your pulldown menu.

Clear as mud?

3 Comments

Thanks @Mallanaga, that works, but it's requiring me to click the dropdown link twice to trigger the js to change the count to 0. The dropdown opens with one click of course, and even if i click outside of the dropdown to close the box, the count doesn't change until I click it again (2 times total). Weird???
not weird. you have to make the change in your js.erb file. I do ('#notifications span.badge').html(0).removeClass('new'); which sets the number to 0 and removes a class that colors the icon.
Thanks, yea I had the code correct but the variable the find the count was in the application controller with a before_action so it woulnd't update. I moved it to the application helper.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.