Hello this is my first question to Stack Overflow. I built a social networking site with Rails 5.0.2. The initial page renders a collection of users and the application layout template includes a chat icon at the top that shows the number of unread messages in a circular red badge.
The red badge is a div styled and positioned by CSS.
Inside the red badge div I render a layouts/_notifications.html.erb
partial to populate the number of unread messages
The notifications partial calls a helper method to return the number of unread messages
The same helper method hides the div if there are no unread messages
The red badge div looks like this:
<div id="unread_conversations" style="<%= "display:none;" if unread_conversations == 0 %>">
<%= render partial: 'layouts/notifications' %>
</div>
The _notifications partial looks like this:
<%= unread_conversations %>
The helper method looks like this:
def unread_conversations
return Conversation.where("(conversations.sender_id = ? OR conversations.recipient_id = ?) AND messages.sender_id != ? AND messages.opened_at IS NULL", current_user, current_user, current_user).joins(:messages).count
end
Additional information:
On receipt of an ActionCable broadcast I re-render the red badge div asynchronously (and this works great):
received: function(data) {
// Called when there's incoming data on the websocket for this channel
value = $("#unread_conversations").html();
value = parseInt(value) + 1;
$("#unread_conversations").html(value);
$("#unread_conversations").show();
$('#sound_element')[0].play();
}
The problem:
After a message has been read...
When the user goes to another page by clicking a link, the red badge flickers on and then disappears after the page completely reloads.
And, if they return using a browser back button the red badge appears and stays on.
The question:
How do I make it so that the red badge div is hidden on all pages once the user reads all their messages?
My attempted (and failed) solutions:
1) Hide the red badge by default in the layout template and then show it using javascript.
<div id="unread_conversations" style="display:none;">
<%= render :partial => 'layouts/notifications' %>
</div>
and
<% if unread_conversations > 0 %>$('#unread_conversations').show();<% end %>
3) Tried to prevent caching of the partial.
render partial: 'layouts/notifications', cache: false
Neither fixed the problem.
Would you please try this out once i think it would work:
<% unless unread_conversations == 0 %>
<div id="unread_conversations">
<%= render partial: 'layouts/notifications' %>
</div>
<% end %>
Related
I have what is essentially a form with multiple pages, where there is a partial which renders the form content for each page inside of a partial that contains the header, footer, "next" button.
I need my "Next" button to respond dynamically to each page. So if the user is viewing Page 1 of the form, then I'm checking that the fields (targets) on Page 1 are not empty before enabling the "Next" button, at which point they move on to Page 2, and the next button should be disabled again until all of the fields/targets on Page 2 have been completed.
So the controller should be able to fire a function each time the page change happens, which I figured I could do by detecting when certain targets are present on the DOM. But I haven't been able to find a straightforward way to do this.
This is more or less a stripped down version of what I have
The outer wrapper:
<body data-controller="next-button">
<main>
<%= turbo_frame_tag :main, do %>
<%= yield %>
<% end %>
</main>
<%= render partial: 'application/progress_bar' %>
<% end %>
</body>
Then I also have a rails form with a text field:
<%= form.text_field :name, data: { target: "next-button.contactName"} %>
This is my controller:
export default class extends Controller {
static targets = ['contactName']
connect() {
console.log(this.contactNameTarget);
}
contactNameTargetConnected() {
console.log('foo');
}
}
When the page loads, console.log(this.contactNameTarget) outputs the HTML for contactName as expected, but contactNameTargetConnected() does nothing.
Based on the stimulus documentation regarding lifecycle callbacks, I would expect this function to fire on load, especially since it clearly can access this.contactNameTarget
I also looked at a posted solution from the stimulus team on github where they have an example that looks like it could solve my issue, but the code they've posted is deprecated, and I was not able to get it working:
const event = document.createEvent("CustomEvent")
event.initCustomEvent("inner-connected", true, true, null)
this.element.dispatchEvent(event)
I am setting my flash message in controller. But flash message is displayed only for few seconds before Vue.js render it's template and flash message disappear. My controller code looks like this:
class ResultsController < ApplicationController
def new
...
end
def create
...
tournament_match = TournamentMatch.find_by(id: tournament_match_id)
if tournament_match.score_cards.attach(params['score_cards'])
flash[:success] = 'Score card uploaded'
else
flash[:warning] = 'Score card upload failed'
end
redirect_to public_results_path
end
end
I want to display flash message bit longer. So how can I display flash message for bit longer? I tried to sleep for 5 seconds in Vue Component in beforeCreate life cycle hook but thats also not working. In Rails I tried flash.keep but that also didn't work. How can I sleep for 5 seconds in Vue.js before rendering the template ?
Update:
My view for flash message looks like below
<div class='flash'>
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>">
<%= value %>
</div>
<% end %>
</div>
So I fixed the problem by making div position: fixed. My final css looks like this
.flash {
position: fixed;
z-index: 10;
}
Vue js was taking all space in the page thats why flash message was invisible, so setting position to fixed makes div remain fixed at the top of page and I can see the flash message.
Ruby on Rails 3. I have a page with several div blocks. I am trying to seperate them into two pages. I want a button at the bottom of the page which will change the rendered partial.
The "render 'news'" is the first partial loaded. I want the "Archived News Postings" button to change the 'news' to 'archive_news'
<%= render 'news' %>
<%= link_to render(:partial => 'archive_news'), :class => 'btn' %> <button type="button" class="archive">Archived News Postings</button>
Can I get a pointer? Thank you
It depends on what you want to achieve really. For instance, these two options could be considered:
1) You could fetch both the news items and the archived ones. Then render two divs with the corresponding news items. The one with news is shown while the other has display: none; in the CSS. This allows you to use JavaScript to toggle the divs visibility. This obviously has some disadvantages with amount of data etc. but it gives an idea what to think about.
Something along these lines:
controller:
def some_action
#items = ...
#archived_items = ...
end
view:
<div id="news">
<% #items.each do |i|
...
</div>
<div id="archived_news">
<% #archived_items.each do |i|
...
</div>
css:
#archived_news {
display: none;
}
2) Another way would be to rely on asynchronously fetching the news items as the used wants to "toggle". This can be done with AJAX. This allows for more flexibility if the data set is large. You can even have "fetch more" in some way that archived news items are loaded on demand if needed.
If you shed some light what you might need it is possible to give a more specific answer.
I am using Angular to send JSON request to the controller, in my controller I flash notice like this:
flash[:notice] = "Toon has been tagged"
After I want to use my rabl template to return JSON response and also include my flash notice content like this:
object #toon
attributes :id, :uuid, :get_tag_string
if flash
node(:flash) do |f|
flash.each do |k, v|
{ :msg => v, :name => k }
end
end
end
attributes :errors
My angular code handles the response and display the flash notice contents correctly. But here comes the problem, when the page refreshed the flash message is displayed again because of following code in my layout view:
<% flash.each do |key, value| %>
<div class="row-fluid">
<div class="span8 offset1"><%= content_tag(:div, value, class: "alert alert-#{key} center")%></div>
</div>
<% end %>
I could remove this or do an after_filter in my controller to call flash.clear
IS THERE A BETTER WAY TO DO THIS?
THANKS!!
I too am using angularjs with rails. The way I am handling error messages from the server (rails) is to use angular route changing events. Since you are dealing with flash messages from the server, it's really the same concept.
In my angular app where I display errors (in your case flash messages) I use an ng-show with a variable, e.g.,
<div ng-show="showMessages" class="alert">
{{myMessages}} // obviously here you may have an ng-repeat or similar
</div>
In my main angular controller I am setting $scope.showMessages (either true or false). At this point it's the same issue. In my controller I use the following callback to see when the route has changed. When it's changed I can set $scope.showMessages to false.
$scope.$on('$routeChangeStart', function (scope, next, current) {
$scope.showMessages = false;
}
I also use this routeChangeStart event to deal with times where I want the message to come out on the next page- by adding another var to control the "show this on the next page only".
I personally wouldn't go back to the server to clear the flash messages - that seems "expensive" whereas you wouldn't be forced to make that extra round trip.
Solve with flash.now in my controller :)
Thanks for your answer Arthur Frankel, liked your way to hand the messages
I have an application written in rails. I have flash messages displaying the user saying he logged in successfully or created or updated his profile. Once the user logs in a flash message("Welcome") is displayed and the user navigates to a different page. If the user clicks BACK button the flash message ("Welcome") is displayed again.
I dont want to disable Cache as it will affect the performance.
Any ideas to tackle this situation will be helpfull.
Thanks.
This is for people coming to this question via search engines.
Problem
When the user clicks on the back button, a fresh request is not made to the server, rather the browser's cached copy is displayed. However, during the first request, the browser has already cached the flash message, and on clicking the back button, the flash message gets displayed again.
Solution
A way to get around this is to use the browser's localStorage or sessionStorage.
Change your code so that the flash messages are shown or hidden via javascript. When the flash message is shown, store that fact in the browser's sessionStorage.
Next time, check if the flash message has already been shown and if so, do not show it again.
I prefer to use sessionStorage instead of localStorage as sessionStorage gets cleaned up on closing the browser. It is helpful if you have too many flash messages. Otherwise, you will have to write the logic for removing older entries
# app/views/layouts/application.html.erb - or wherever your flash messages reside
...
<%
flash_name_mappings = {
"notice" => "success",
"error" => "danger",
"alert" => "warning"
}
%>
<% flash.each do |name, msg| %>
<% if msg.is_a?(String) %>
<% flash_token = Time.current.to_s(:number) # or use any other random token generator, like Devise.friendly_token %>
<div id="flash-<%= flash_token %>" style="display:none" class="alert alert-<%= flash_name_mappings[name] || name %>">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<%= content_tag :div, msg.html_safe, id: "flash_#{name}" %>
</div>
<script type="text/javascript">
$(document).ready(function(){
if (typeof(Storage) !== "undefined") {
// Code for localStorage/sessionStorage.
if(sessionStorage["flash-<%= flash_token %>"] != "shown"){
$("#flash-<%= flash_token %>").show();
sessionStorage["flash-<%= flash_token %>"] = "shown";
}
} else {
// Sorry! No Web Storage support..
$("#flash-<%= flash_token %>").show();
}
}
</script>
<% end %>
<% end %>
...
Try to use Flash.now instead:
flash.now[:notice] = "Welcome"
In comparison to flash method it displays the message only until the next action is performed. Normal flash message is displayed until the next request is send to the server. I hope it helps.