Rails updating several times on only one update request - javascript

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.

Related

Django: Launch a server-side computation and dynamically update the client

I am creating a web application with Django (back end) and JavaScript (front end).
The main scenario is the following: a client asks the server to train a neural network (which can take quite a lot: seconds or even minutes, sometimes). Immediately after sending the request, the client shows to the user how the training session is going; it does so by retrieving and displaying the training error, which is actually just a number.
For training the network I made this function, which I implemented as a generator:
def train(network, trainset):
# [setup variables...]
while (current_error > target_error):
# [continue to train the network...]
# [update current_error...]
yield current_error
On the front end (a simple HTML page) I have a JS script for retrieving data dynamically from a Django view. My Django view goes something like this:
def getError(request):
# ...
generator = train(net, dataset)
return StreamingHttpResponse(generator)
At the moment I can retrieve the data with an AJAX call but I always have to wait for the server to train the whole neural network before I can see the training errors.
My problem is that I would like the user to view the error updates while the server is training the network, not after: that's why I implemented the training function as a generator (and that's why I chose to return a StreamingHttpResponse).
What can I do for retrieving the data while the server is computing? Specifically, I would like to update the front end each time the training generator yields a number.
Initial analysis:
Let's have a look at the documentation:
yield:
The yield expression is used when defining a generator function
generator:
A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.
From the aforementioned it is clear that your code sample works well but not as intended:
The train() function yields a generator which gets "transmitted" to your front-end from the getError after the whole calculation is done.
Reform the problem based on analysis:
With the above mentioned, your problem will be to get the calculations of each step of the train() process and display them to the front-end.
A skeleton of a solution:
Create a global variable (yes I said it) list.
Refactor train() function to use the previous list:
train_errors = []
def train(network, trainset):
# [setup variables...]
while (current_error > target_error):
# [continue to train the network...]
# [update current_error...]
train_errors.append(current_error)
Assuming that train() is located in views.py file, refactor the getError() to return the train_errors list:
from django.http import JsonResponse
def getError(request):
# ...
return JsonResponse(train_errors, safe=False)
I utilize JsonResponse, but feel free to use anything that suites you.
Make your front-end to repeat the AJAX call every N seconds, by utilizing javascript's setInterval, and display the differences from the previous state of the data.
Example of use:
var myIntervalCall = setInterval(myAJAXcall, 500);
function myAJAXcall{
// Create your AJAX call in here
// And sufficient code to display the changes on the received data.
}
The above code will make myAJAXcall to the back-end, every 0.5 sec
The above will allow you to make an initial approach to your problem and you can/must expand on this (barbaric) solution.
EDIT/Disclaimer (due to #George and #brunodesthuilliers comments):
The above, is not a good solution!! It is only a quick solution to get the author "unstuck" from his current problem and help him move on with his development. The "solution" provides the basis for a thought process, considering the problem at hand (the actual problem that the author has at the moment) and therefore provides a minimal and initial approach to a solution.
The aforementioned distiled: DO NOT TAKE THIS SOLUTION AS CANON! it is only a "jump start".
Solution 1: use celery for the computation, storing progress in redis, and have your front side js code poll a view that will get progress from a dedicated view.
Solution 2: use a websocket.

How can I test in Capybara that a page has *not* reloaded (JavaScript onClick intercept has worked)?

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

How to stay DRY when using both Javascript and ERB templates (Rails)

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.

Capybara doesn't recognize dynamically added DOM elements?

I seem to be having trouble testing the slick javascript things I do with jQuery when using Capybara and Selenium. The expected behavior is for a form to be dynamically generated when a user clicks on the link "add resource". Capybara will be able to click the link, but fails to recognize the new form elements (i.e. "resource[name]").
Is there a way to reload the DOM for Capybara, or is there some element of this gem that I just haven't learned of yet?
Thanks in advance!
==Edit==
Currently trying my luck with selenium's:
wait_for_element
method.
==Edit==
I keep getting an "undefined method 'wait_for_element` for nill class" when attempting to do the following:
#selenium.wait_for_element
It appears that that specific method, or perhaps wait_for with a huge selector accessing the DOM element I expect is the correct course of action, but now trying to get the selenium session is starting to be a huge headache.
I use the Webdriver based driver for Capybara in RSpec, which I configure and use like this and it will definitely handle JS and doesn't need a reload of the dom. The key is using a wait_until and a condition that will be true when your AJAX response has finished.
before(:each) do
select_driver(example)
logout
login('databanks')
end
def select_driver(example)
if example.metadata[:js]
Capybara.current_driver = :selenium
else
Capybara.use_default_driver
end
end
it "should let me delete a scenario", :js=>true do
select("Mysite Search", :from=>'scenario_id')
wait_until{ page.has_content?('mysite_searchterms')}
click_on "delete"
wait_until{ !page.has_content?('mysite_searchterms')}
visit '/databanks'
page.should_not have_content('Mysite Search')
end
I also figured out a hack to slow down webdriver last night, like this, if you want to watch things in slo-mo:
#set a command delay
require 'selenium-webdriver'
module ::Selenium::WebDriver::Remote
class Bridge
def execute(*args)
res = raw_execute(*args)['value']
sleep 0.5
res
end
end
end
As someone else mentioned, if you are getting a timeout waiting for the element, you could look at upping this:
Capybara.default_wait_time = 10
From the Capybara docs:
When working with asynchronous
JavaScript, you might come across
situations where you are attempting to
interact with an element which is not
yet present on the page. Capybara
automatically deals with this by
waiting for elements to appear on the
page.
You might have some luck increasing the wait time:
Capybara.default_wait_time = 10
If that doesn't help then I would encorage you to contact somebody from the project on GitHub, write to the mailing list or submit an issue report.
Even wait_until deleted from Capybara 2.0. Still that's useful and grab code from below:
def wait_until(delay = 1)
seconds_waited = 0
while ! yield && seconds_waited < Capybara.default_wait_time
sleep delay
seconds_waited += 1
end
raise "Waited for #{Capybara.default_wait_time} seconds but condition did not become true" unless yield
end

Why is the proper "respond_to" format not getting called?

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?

Categories