I'm a complete novice in JavaScript/jQuery and I believe it's a very simple question; however I'm not being able to accomplish it.
I have an asynchronous task being performed (by sidekiq) and it's progress is available by a method from the model (percentage_complete) that retrieves its progress from Redis.
I want to display a progress bar in model's show view, and I want it to update every x seconds using AJAX.
The progress bar is being displayed like this on the show.html.erb file:
<div class="progress">
<div class="bar" style="width: <%= #model.percentage_complete %>%;"></div>
</div>
How can I set a jQuery script to update this attribute asynchronously?
EDIT
I also have a a :status attribute which is set do "done" when the task is complete. I would like to stop updating when that happens.
By reading my question it appears that I haven't tried nothing and just want someone to write the code for me. Let me add some comments:
I know I should use setInterval to update the attribute every "x" seconds
I know I should use $('.progress .bar').width(<%= #model.percentage_complete %>%) to set the new percentage
However, since I'm not familiar to jQuery and JavaScript, specially in Rails, I'm not sure if this script should be loaded in a view, or if it should be a view itself.
I solved it by creating an action to retrieve the status
# GET /status/1.json
def status
#batch = Batch.find(params[:id])
respond_to do |format|
format.json
end
end
and using the following JavaScript:
<script type="text/javascript">
function progress(){
var progress = setInterval(function() {
var $bar = $('.bar');
var $pct = $('#pct');
$.get("<%= #batch.id %>/status.json", function(data){
if (data.status == "done") {
location.reload();
} else {
$bar.width(data.progress+"%");
$pct.text(data.progress+"%");
}
});
}, 800);
}
$(document).ready(function() {
$.get("<%= #batch.id %>/status.json", function(data) {
if (data.status == "processing") {
progress();
}
});
});
</script>
Related
I have been stuck on this problem for quite some time now and looked through several posts as well, however I cannot achieve exactly what I want for my Rails application. Essentially, I want to be able to click on a table row on my page and have a modal pop up which displays all the information for that specific record. Here are the scenarios which I have thought of/attempted partially:
Set the data-link attribute in table row with some JS as follows
HTML:
<tr data-link="<%= kid_path %>">
...
</tr>
JS:
$("tr[data-link]").dblclick(function() {
window.location = $(this).data("link")
})
This worked fine to open the show path page generated by the scaffold, but I was not able to modify it to work with a modal and have the same data for the kid in the modal.
Use data-id and JavaScript to load onto the modal
HTML:
<tr data-id="<%= kid.id %>">
...
</tr>
JS:
$(function () {
$('#showModal').modal({
keyboard: true,
backdrop: "static",
show: false,
}).on('show', function () {
});
$(".table-striped").find('tr[data-id]').on('click', function () {
debugger;
$('#showDetails').html($('<p>' + 'Kid ID: ' + $(this).data('id') + '<%= Kid.find(30).first_name %>' + '</p>'));
$('#showModal').modal('show');
});
});
In this approach I am able to load the modal on row click and am able to access the Kid ID, however I cannot move further to access other attributes of the record. For example, I want to set #Kid = kid.find(id) using JS where id would be the extracted ID from the row. And then, I want to be able to write the generic modal template which displays other elements (ex. kid.first_name, kid.last_name, etc).
I am totally stuck and cannot find any approach that helps to accomplish my goal. Any help is appreciated, thank you.
You need to ajax call record attributes because the line Kid.find(30).first_name doesn't exist at the time page loaded.
Try this:
KidsController
def show
kid = Kid.find(params[:id])
respond_to do |format|
format.html { // Usually show.html.erb }
format.json do
# Return kid as json object
result = {
first_name: kid.first_name,
last_name: kid.last_name
}
# If you want to return the whole kid record attributes in json: result = kid.attributes.to_json
render json: result
end
end
end
Try /kid/[:id].json to verify that you are not getting UnknownFormat error.
JS
$(".table-striped").find('tr[data-id]').on('click', function () {
var kid_id = $(this).data('id');
$.getJSON("/kid/" + kid_id, function(data) {
// Render the modal body here
// first_name = data.first_name, last_name = data.last_name etc
$('#showDetails').html($('<p>'+ data.first_name + '</p>'));
$('#showModal').modal('show');
});
})
If you have setup correct route for Kid model then these are what you needed.
UPDATED: I made a typo in the result hash. FIXED
In my Backbone view here, I want to be able to immediately add my model to my collection with an 'id' attribute included in the newly created model. In other words, I need to have access to this 'id' right away. With the id available, my removeCategory method can work. To "solve" that problem, I tried to add the line this.collection.fetch() directly below this.collection.add(model);, but it gave me very bad UI (the new <li> wouldn't even render, etc), plus it seems like very bad coding practice anyway.
I'm relatively new with Backbone, so I'm not sure if create (save/add) should automatically add the model to the collection with an id or not. If it should, then there must be something wrong with the JSON data I'm receiving from my Rails RESTful API. If create does NOT do that, then how can I instantly get access to this specific newly created model, with an id (other than by refreshing the browser -- ugh!)? I need the id for <li data-id={{id}}></li> in my Handlebars template as soon as I invoke the "addCategory" method.
Also (& this might be a related problem), when I do model.fetch();, I get back the model's entire collection, plus that newly-created server-side model without an id. Any help on this would be much appreciated.
window.CategoriesView = Backbone.View.extend({
index_template: HandlebarsTemplates['categories/index'],
events: {
'click .delete': 'removeCategory',
'click .category_add': 'addCategory'
},
initialize: function() {
_.bindAll(this, 'render');
this.collection.fetch();
this.listenTo(this.collection, 'all', this.render);
},
show: function() {
$("#categorymodal").modal();
},
addCategory: function(e) {
e.preventDefault();
// I wrote the next three lines here explicitly rather than using 'create'
var model = new Category({ name: $(this.el).find('.category_name').val() });
model.save();
this.collection.add(model);
this.show();
},
removeCategory: function(e) {
e.preventDefault();
var id = $(e.target).parents('li').data('id');
var model = this.collection.where({ id: id })[0];
this.collection.remove(model);
if (model) { model.destroy(); }
this.render();
},
render: function() {
$(this.el).html(this.index_template());
this.collection.models.forEach(function(cat) {
var li_template = HandlebarsTemplates['categories/show']
$(this.el).find('.categories').append(li_template({
id: cat.toJSON().id,
name: cat.toJSON().name
}));
}, this);
return this;
}
});
Here is my #create action in Rails....
def create
#category = Category.create(category_params)
render :nothing => true
end
I am using jbuilder to provide the JSON from the Rails API.
Here is my Backbone model and collection...
window.Category = Backbone.Model.extend({
urlRoot: "categories"
});
window.Categories = Backbone.Collection.extend({
model: Category,
url : function() {
return 'categories';
}
});
I figured out my problem. It was in my Rails controller, not with Backbone create. I changed my #create action from this...
def create
#category = Category.create(category_params)
render :nothing => true
end
...to this....
def create
#category = Category.create(category_params)
redirect_to category_path(#category)
end
...with my show action being this...
def show
#category = Category.find(params[:id])
end
...that provided me with http://localhost:3000/categories/752.json. I've gotta make sure my backend is providing what I need JSON-wise!
I am working on a web app and I use Ruby on Rails. Our index is made of a map and of a search field. You can search a location and the map updates its markers.
I would like to use Ajax to avoid refreshing the page. So I added remote: true to the form, a respond_to in the controller and a new search.js.erb. My search.js.erb renders a partial _googlemap.erb which contains the script to initialize the map.
Here is my problem. As the map already exists, it's like if I wanted to create the same object twice, which of course doesn't work and is awfull. I'd like to update only markers in the map with new ones. But I can't find a way to do it.
I saw the former version of Gmaps4rails integrated a way to do it ( Gmaps.map.replaceMarkers(your_markers_json_array); ) but it doesn't seem to work now. When I use it, I got this error: "TypeError: Gmaps.map is undefined". I tried with "handler.replaceMarkers();" but this time I have "TypeError: handler.replaceMarkers is not a function".
I am new to Javascript and to Rails, but I want to improve my knowledge and I really need to go on with the rest of this web app. I have been looking for a solution everywhere on the internet but in vain.
Live website here
Please, could someone tell me how I could do that and what I am doing wrong?
MANY thanks in advance,
CĂ©line
zones_controller.rb
def search
respond_to do |format|
format.html.none do
search_params = params[:zone][:search]
coordinates = Geocoder.coordinates(search_params).join(",")
#zones = Zone.search(
"", { "aroundLatLng" => coordinates,
"aroundRadius" => 500000 #Searches around 500 km
})
if coordinates.nil?
#zones = Zone.search(params[:search])
elsif #zones.empty?
#zones = Zone.all
flash[:error] = "No zone could be found. Please try again."
end
build_map(#zones)
end
format.js
end
end
def build_map(array)
#hash = Gmaps4rails.build_markers(array) do |zone, marker|
marker.lat zone.latitude
marker.lng zone.longitude
marker.json({ title: zone.description, id: zone.id })
marker.infowindow render_to_string(:partial => "/zones/map_box", locals: { zone: zone })
end
end
search.html.erb
<div id="map" style='width: 100%; height: 700px;'>
</div>
<!-- Beginning Google maps -->
<script type="text/javascript" id="map_script">
$(document).ready(function() {
<%= render 'googlemap', hash: #hash %>
}); // Document ready
</script>
_googlemap.erb
handler = Gmaps.build('Google');
handler.buildMap({ provider: {
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId.TERRAIN
}, internal: {id: 'map'}
}, function(){
markers_json = <%= raw hash.to_json %>;
markers = _.map(markers_json, function(marker_json){
marker = handler.addMarker(marker_json);
handler.getMap().setZoom(4);
_.extend(marker, marker_json);
marker.infowindow = new google.maps.InfoWindow({
content: marker.infowindow
});
return marker;
});
handler.bounds.extendWith(markers);
handler.fitMapToBounds();
});
search.js.erb
$('#map_script').replaceWith("<%= render 'googlemap', hash: #hash %>");
Why don't you just update the map with the new markers? Meaning, instead of re-rendering the whole map after each search, just update the markers on the existing map by removing all markers and adding the new ones.
I haven't verified the method, but I guess it should work and be more efficient:
Create a app/assets/javascript/map.js file. You can store your map-related functions there. Create a function to update your map's markers in this file:
map.js
(function() {
/* __markers will hold a reference to all markers currently shown
on the map, as GMaps4Rails won't do it for you.
This won't pollute the global window object because we're nested
in a "self-executed" anonymous function */
var __markers;
function updateMarkers(map, markersData)
{
// Remove current markers
map.removeMarkers(__markers);
// Add each marker to the map according to received data
__markers = _.map(markersData, function(markerJSON) {
marker = map.addMarker(markerJSON);
map.getMap().setZoom(4); // Not sure this should be in this iterator!
_.extend(marker, markerJSON);
marker.infowindow = new google.maps.InfoWindow({
content: marker.infowindow
});
return marker;
});
map.bounds.extendWith(__markers);
map.fitMapToBounds();
};
// "Publish" our method on window. You should probably have your own namespace
window.updateMarkers = updateMarkers;
})();
This function can be used to initialize your map and to update it. As you will not (if my answer satisfies you) render the map twice, you can delete _google_map.erb and put its content back into search.html.erb, but using the function we've just created:
search.html.erb
<div id="map" style='width: 100%; height: 700px;'>
</div>
<!-- Beginning Google maps -->
<script type="text/javascript" id="map_script">
$(document).ready(function() {
mapHandler = Gmaps.build('Google');
mapHandler.buildMap({ provider: {
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId.TERRAIN
}, internal: {id: 'map'}
}, function(){
var markersData = <%= raw #hash.to_json %>;
updateMarkers(mapHandler, markersData);
});
}); // Document ready
</script>
Please don't forget the var keyword when declaring variables, otherwise they will end up being globals, and that's bad ^^
Note that I have deliberately left mapHandler as a global variable: you will need access to your handler to update markers later when someone uses the search. This is probably not an ideal thing to do, but this question is not about refactoring your code so let's keep it this way.
So now my solution brings you a map that initializes with the given markers on page load. In other words, nothing has changed!
However you're now allowed to reuse this updateMarkers function to change the markers displayed on your map. That's what you search.js.erb script should do:
search.js.erb
(function() {
var markersData = <%= raw #hash.to_json %>;
updateMarkers(mapHandler, markersData);
})();
That's it! Hopefully it'll take you to the next step of your project :)
I tried the same thing but instead of updating the marker you should include the map in the partial/placeholder and then update it ...
for example, this is the view which is displaying the map...i will update this view/placeholder with latest map and markers
<div id="map_users_index">
<div id="map" class="map" style="height:relative;">
</div>
in users_controller.rb
##take index action or any other action
def index
##do your stuff and get more users
respond_to do |format|
format.html
format.js
end
end
in index.js.erb
##update the placeholder/partial with new map with new markers
$("#map_users_index").html("<%= escape_javascript(render(:partial => 'index')) %>");
I HAVE MY OWN WORKING CODE...HERE
I have datatable and a div, where I want to load information about the order with ajax after clicking on the row of the datatable.
assets/javascript/orders.js:
$(document).ready(function() {
$('#orders').dataTable({
"sPaginationType": "full_numbers",
"bJQueryUI": true
});
$('#orders tbody tr').each( function() {
this.onclick = function() {
$('#order_info').load(
???
)
};
})
})
orders/list.html.erb:
<table id="orders">
<tbody>
<% #orders.each do |order| %>
<tr data-orderrow=<%= order[:id] %>>
</tr>
<% end %>
</tbody>
</table>
<div id="order_info" style="float: right;">
</div>
If my approach is good, what should I put in the place of ??? in my javascript.
You can make an ajax call to some controller and action that returns json data. Then consume that data, and display the json that comes back.
$(document).ready(function() {
// best to 'cache' the jquery queries you use more than once.
var $orders = $("#orders"),
$orderInfo = $("#orderInfo");
$orders.dataTable({
"sPaginationType": "full_numbers",
"bJQueryUI": true
});
// it is best not to attach one click even to each 'tr', so instead
// I've added a single click event to the entire table, and then
// determine the closest 'tr' to get the orderId from it.
$orders.click(function(e){
var $target = $(e.target), $order, orderId; // get the element that was clicked
$order = $target.closest("tr");
orderId = $order.attr("data-orderrow");
$.getJSON("/orders/"+orderId+".json",function(orderData){
// do something with orderData, which is an object with the order data
});
});
})
Then in your controller, you could do something like this. You'll need to fill this out in the best way for your application:
class SomeController < ApplicationController
def show
order = Order.find(params[:id])
respond_to do |format|
format.html { render "show" }
format.json { render :json => order }
end
end
end
I'm trying to update the content of "mydiv" without refreshing the entire index page.
#mydata is given by mycontroller. I need to recalculate it every n seconds and pass it to "mydiv"
With "link_to" it works!
index.html.erb
<%=
link_to('refresh', '/mycontroller/index', :remote => true)
%>
<div id="mydiv">
<%=
#mydata
%>
</div>
index.js.erb
$('#mydiv').html('<%= escape_javascript(#mydata) %>')
Now I need to refresh the content of "mydiv" automatically every n seconds (so without click on the link). I have tried solutions from:
First Link
Second Link
but no luck.
In my application.js I have writed this:
function executeQuery() {
$.ajax({
//url: '/index',
success: function(data) {
$('#mydiv').html(data)
}
});
setTimeout(executeQuery, 500);
}
$(document).ready(function() {
setTimeout(executeQuery, 500);
});
For who is facing my same problem, I solved it by replacing
$('#mydiv').html(data)
with
$('#mydiv').load('/mycontroller/index #mydiv')
Use setInterval() instead of using setTimeout().
Ref: https://www.w3schools.com/jsref/met_win_setinterval.asp
function executeQuery() {
$.ajax({
type: 'GET',
url: 'example.com/url/', // Provide your response URL here.
success: function(data) {
$('#mydiv').html(data);
}
});
}
setInterval(executeQuery(), (n * 1000)); // Replace 'n' with how many seconds you want.
This code will run the executeQuery() method in every 'n' seconds interval. So that your requirement is accomplished.
Set layout to false in the action and just pass on the relevent content, not the entire page
def action1
<your code here>
end
def action2
<your code here>
render :layout => false
end
Your view for action2 should have content pertaining only to #mydiv.
A better solution would be to use a single action and change render options based on type of request. (Ajax or non ajax)