I'm having a bit of an odd issue. Really too odd to type out, but here goes. Basically I have a controller that refuses to "respond_to" using javascript unless I assign my "chart.generate_xml" to a variable before the "respond_to" block like so:
#xml = #chart.generate_xml(#begin_date,#end_date,1.hour)
respond_to do |format|
format.html
format.js{
render :update do |page|
page.insert_html :bottom, "chart-div", #xml
#page.insert_html :bottom, "chart-div", #chart.generate_xml(#begin_date,#end_date,1.hour)
end
}
If I remove the upper "#xml= …" portion and go with the lower "page.insert", the "format.js" section doesn't get called. And if I try to force the format with "request.format = :js", I get the javascript returned as text. I'm not doing anything special here in that method call, so I'm not sure why it would choose to respond any differently.
FWIW, the method that triggers this controller action is using JS to do so, so I'm confused as to why "format.js" isn't always getting called. Thoughts?
Best.
It could be an issue in your config/routes.rb file, as this can mess with the format.
Could you post this file and the header of the results from curl/wget?
Related
I have an editor where users can edit and paste text, and the changes are automatically saved in the server, via AJAX.
Through JS I monitor the state of the textarea, like this:
$("#editor-area").bind "input propertychange", ->
content = $("#editor-area").val()
updateRequest = $.ajax(
url: url
data:
texto:
contenido: content
type: "PUT"
)
Which I extracted from here, answer by Emma Li.
The moment I write a single character, the console shows the same information when one call is performed and processed, but many times. They are so many that my console is overrun and starts overwriting past calls, doing the exactly same thing: updating the record with the new text.
The update method in the controllers looks like
def update
respond_to do |format|
if #text.update(text_params)
format.html {
redirect_to user_text_path(current_user, #text)
}
format.json { render :json => #texto }
else
format.html { redirect_to edit_user_path(#user, #text) }
end
end
end
There also a before_filter for that option
def set_text_and_user
#text = get_stuff_from_Db
#user = #text.user
end
I've been trying to debug this thing, but it has defeated me so far. Have any idea what could be causing this?
I actually think, the accepted answer is better.
I've never used the propertychange event, and quick googling shows that - this might not be 100% correct - it's actually an IE-specific event. I'm not sure how (and if) it is supported by other browsers. I do think that you only really need to listen for keyup event on textarea.
Anyway, to the point of your question. There are different ways you can go. They all come down to using a timer. You most likely don't want to send every single keypress to your backend as several concurrent users will simply kill it.
Most common approach is probably throttling. In its simplest, it's a function that catches your events checks the time difference since the previous event and calls the handler only if enough time has passed. You can google for the particulars, I'm pretty sure there are jQuery plugins for that.
I'm using the gon gem for rails, which allows you to save variables defined in a controller action and use them in your JavaScript. It works fine when I use it in non-Ajax settings, however, I'm having an issue with using it successfully when doing Ajax requests.
The problem: Ruby variables I assign to gon variables in the controller action when making Ajax requests come out as 'undefined' in the JavaScript.
The objective: I want to trigger an Ajax request on my page, which:
1) hits an action in the controller, and assigns a Ruby variable to a gon variable.
2) it then renders a js.erb file which executes JavaScript, part of which needs to take the Ruby variable defined in step 1, and treat it as a js variable.
here's the example action in step 1:
def some_action
gon.my_ajax_var = {some: 'info'}
end
here's the example js.erb file it renders:
/some_action.js.erb
console.log('gon.my_ajax_var equals ' + gon.my_ajax_var) //this doesn't work! comes out as 'undefined' when I expected {some: 'info'}
Any thoughts on how I fix this? I took a look at the gon.watch page, but I was confused as to whether that relates to this problem I'm having and how to implement the correct solution. Additionally, if there's a better way to do this without gon, I'm open to that as well.
Thanks!
I ended up solving this by doing the following:
In my controller:
def some_action
#my_ajax_var = {some: 'info'}.to_json
end
In my corresponding view:
/some_action.js.erb
var my_ajax_var = <%= #my_ajax_var.html_safe %>
Would've been nice to have piggybacked off the gon gem, but this got the job done.
It's some time ago that I used erb templates, but I think you need to add tags in your erb-file.
/some_action.js.erb
console.log('gon.my_ajax_var equals ' + <%= gon.my_ajax_var %>)
I am using Capybara, Cucumber and Poltergeist. I am testing a JavaScript function that is attached to a form submit button, which is intended to catch the submit event and prevent it (doing an AJAX request in the background). With and without AJAX, the page will end up looking the same, but the AJAX approach is much faster and does not interrupt the browsing experience etc.
What can I do to test that the form was indeed not submitted, and that the changes are the result of a dynamic AJAX call rather than a reload?
Modified version of #jules' answer:
describe "My page", :js do
it "reloads when it should" do
visit "/"
expect_page_to_reload do
click_link "Reload me"
end
end
def expect_page_to_reload
page.evaluate_script "$(document.body).addClass('not-reloaded')"
yield
expect(page).not_to have_selector("body.not-reloaded")
end
end
EDIT 2017-01-19:
I found this old answer of mine which strangely did not seem to answer the actual question, but instead the opposite (check that the page has reloaded). This is what we do currently in our app to check that a page does not reload:
def expect_page_not_to_reload
page.evaluate_script %{$(document.body).addClass("not-reloaded")}
expect(page).to have_selector("body.not-reloaded")
yield
sleep 0.5 # Give it a chance to reload before we check.
expect(page).to have_selector("body.not-reloaded")
end
Both answers rely on jQuery for adding the class.
I had the same issue and came up with a solution, probably not the best one–but it works :)
#support/reload.rb
def init_reload_check
page.evaluate_script "window.not_reloaded = 'not reloaded';"
end
def not_reloaded
page.evaluate_script("window.not_reloaded") == "not reloaded"
end
#Ajax Test
it "should not reload the page", js: true do
init_reload_check
click_link "Ajax Link"
expect(not_reloaded).to be_truthy
end
And here's my version of Henrik N's answer. Doesn't rely on jQuery or whatever assertion library he's using.
def assert_page_reloads(message = "page should reload")
page.evaluate_script "document.body.classList.add('not-reloaded')"
yield
if has_selector? "body.not-reloaded"
assert false, message
end
end
def assert_page_does_not_reload(message = "page should not reload")
page.evaluate_script "document.body.classList.add('not-reloaded')"
yield
unless has_selector? "body.not-reloaded"
assert false, message
end
page.evaluate_script "document.body.classList.remove('not-reloaded')"
end
Capybara is intended for user-level functional testing. There isn't an easy way to test an implementation detail such as whether a form uses AJAX or a traditional form submission. It encourages you to write your tests in terms of the end-user experience.
There are a couple of things you can do:
Capybara supports a wait time for finders and assertions. There are a few ways to set it, using Capybara.default_wait_time, Capybara.using_wait_time, or passing a wait option to your assertion methods. If you set a low wait time, you can be sure that the results of clicking the submit button return quickly.
Look into JavaScript unit and integration testing frameworks such as Jasmine. This can be used to test the JavaScript code that sets up the event binding and AJAX call. You can use a spy to ensure that the expected AJAX call is made when the button is clicked.
feature "User creates comment", do
context "with JavaScript enabled", js: true do
scenario "creates comment via AJAX" do
initial_page_id = assign_page_id
# Perform AJAX action
...
page_reloaded = current_page_id != initial_page_id
expect(page_reloaded).to eq(false)
end
def assign_page_id
page_id = SecureRandom.hex
page.evaluate_script "window.pageIdForTesting = '#{page_id}'"
end
def current_page_id
page.evaluate_script("window.pageIdForTesting")
end
end
context "without JavaScript" do
# Can be used if progressively enhancing
...
end
end
I've added JavaScript partials many times before, but admittedly not for a while. The short of it is, I'm missing something stupid.
When I try to render partial 'deleteme.js' at the end of this view:
/app/views/projects/myview.html.slim:
h2 Stuff goes here
render partial: '/projects/thisworks' # Render html partial
render partial: '/projects/deleteme.js' # Render js partial...fails
Which renders this partial:
/app/views/projects/_deleteme.js.erb:
console.log("Oh FFS, Work");
I get:
ActionView::MissingTemplate in Projects#show
Showing
/Users/aj/Web/Rails/sh3/app/views/projects/show.html.slim
where line #5 raised:
Missing partial /projects/deleteme with {:locale=>[:en],
:formats=>[:html], :handlers=>[:erb, :builder, :slim, :jbuilder,
:coffee, :haml]}. Searched in: *
...
The incriminating bit is that it's searching for formats => [:html] only, but I thought appending .js automatically tells rails to search for javascript. If anyone has any ideas as to why it's not finding the JS template, I'd be really appreciative. Action code is a very basic:
def show
#project = Project.find(params[:id])
end
/app/views/projects/_deleteme.js.erb:
You are getting partial naming intertwined with Javascript action naming!
/app/views/projects/deleteme.js.erb:
rename your javascript file, remove the _
You have to make sure your show responds to js
def show
respond_to do |format|
format.js
format.html
end
end
You are calling a partial inside a slim template so it expects to find
app/views/projects/_deleteme.js.slim
I'm building a Rails app that uses Pusher to use web sockets to push updates to directly to the client. In javascript:
channel.bind('tweet-create', function(tweet){ //when a tweet is created, execute the following code:
$('#timeline').append("<div class='tweet'><div class='tweeter'>"+tweet.username+"</div>"+tweet.status+"</div>");
});
This is nasty mixing of code and presentation. So the natural solution would be to use a javascript template. Perhaps eco or mustache:
//store this somewhere convenient, perhaps in the view folder:
tweet_view = "<div class='tweet'><div class='tweeter'>{{tweet.username}}</div>{{tweet.status}}</div>"
channel.bind('tweet-create', function(tweet){ //when a tweet is created, execute the following code:
$('#timeline').append(Mustache.to_html(tweet_view, tweet)); //much cleaner
});
This is good and all, except, I'm repeating myself. The mustache template is 99% identical to the ERB templates I already have written to render HTML from the server. The intended output/purpose of the mustache and ERB templates are 100% the same: to turn a tweet object into tweet html.
What is the best way to eliminate this repetition?
UPDATE: Even though I answered my own question, I really want to see other ideas/solutions from other people--hence the bounty!
imo the easiest way to do this would involve using AJAX to update the page when a new tweet is created. This would require creating two files, the first being a standard html.erb file and the second being a js.erb file. The html.erb will be the standard form which can iterate through and display all the tweets after they are pulled from the database. The js.erb file will be your simple javascript to append a new tweet upon creation, i.e.:
$('#timeline').append("<div class='tweet'><div class='tweeter'><%= tweet.username %></div><%= tweet.status %></div>")
In your form for the new tweet you would need to add:
:remote => true
which will enable AJAX. Then in the create action you need to add code like this:
def create
...Processing logic...
respond_to do |format|
format.html { redirect_to tweets_path }
format.js
end
end
In this instance, if you post a tweet with an AJAX enabled form, it would respond to the call by running whatever code is in create.js.erb (which would be the $('#timeline').append code from above). Otherwise it will redirect to wherever you want to send it (in this case 'Index' for tweets). This is imo the DRYest and clearest way to accomplish what you are trying to do.
Thus far, the best solution I found was Isotope.
It lets you write templates using Javascript which can be rendered by both the client and server.
I would render all tweets with Javascript. Instead of rendering the HTML on the server, set the initial data up as JS in the head of your page. When the page loads, render the Tweets with JS.
In your head:
%head
:javascript
window.existingTweets = [{'status' : 'my tweet', 'username' : 'glasner'}];
In a JS file:
$.fn.timeline = function() {
this.extend({
template: "<div class='tweet'><div class='tweeter'>{{tweet.username}}</div>{{tweet.status}}</div>",
push: function(hash){
// have to refer to timeline with global variable
var tweet = Mustache.to_html(timeline.template, hash)
timeline.append(tweet);
}
});
window.timeline = this;
channel.bind('tweet-create', this.push);
// I use Underscore, but you can loop through however you want
_.each(existingTweets,function(hash) {
timeline.push(hash);
});
return this
};
$(document).ready(function() {
$('#timeline').timeline();
});
I haven't tried this, but this just occurred to me as a possible solution:
In your view create a hidden div which contains an example template (I'm using HAML here for brevity):
#tweet-prototype{:style => "display:none"}
= render :partial => Tweet.prototype
Your tweet partial can render a tweet as you do now.
.tweet
.tweeter
= tweet.username
.status
= tweet.status
When creating a tweet prototype you set the fields you want to the js-template replacement syntax, you could definitely dry this up, but I'm including it here in full for example purposes.
# tweet.rb
def self.prototype
Tweet.new{:username => "${tweet.username}", :status => "${tweet.status}"}
end
On the client you'd do something like:
var template = new Template($('#tweet-prototype').html());
template.evaluate(.. your tweet json..);
The last part will be dependent on how you're doing your templating, but it'd be something like that.
As previously stated, I haven't tried this technique, and it's not going to let you do stuff like loops or conditional formatting directly in the template, but you can get around that with some creativity I'm sure.
This isn't that far off what you're looking to do using Isotope, and in a lot of ways is inferior, but it's definitely a simpler solution. Personally I like haml, and try to write as much of my mark up in that as possible, so this would be a better solution for me personally.
I hope this helps!
To be able to share the template between the javascript and rails with a mustache template there is smt_rails: https://github.com/railsware/smt_rails ("Shared mustache templates for rails 3") and also Poirot: https://github.com/olivernn/poirot.