How do I limit mouse click rate in a Rails app? - javascript

I've written a timecard app, and I have a page where people can "punch" in and out. Each person has a button which makes an Ajax call back to change their status. A couple of folks have figured out that it's fun to click on a person's button a hundred times in a row, so that the report page for clock in/out times gets really goofy.
I'd like to put a simple Javascript function in the punch page which would limit the "clickability" of the page to something like once every 5 seconds. I've found a couple of other threads here talking about something like that, but I don't know enough about JS to graft those suggestions into how Rails is making the Ajax calls.

Well, there are several possibilities; depending on how much control you'd like.
First you could limit in the JS how often the request can be made. As mentioned earlier.
Also, you could make it part of the model logic, not accepting updates within a certain time (also mentioned earlier)
My suggestion would be to rate limit the request per user. There is a nice rack middleware for that: http://datagraph.rubyforge.org/rack-throttle/. If needed you could add customisations, but it's a good start I think.

Couldn't you check the updated_at field on your record? It won't limit the "mouse" click rate, but it will keep your record from getting updated more than x many times per minute/hour.
For example, in your model, you could have something like:
class TimeRecord < ActiveRecord::Base
validate :throttled_updates, on: :update
private
def throttled_updates
errors.add(:updated_at, "must be more than 5 minutes ago") if self.updated_at < 5.minutes.ago
end
end

http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to
Option: disable-with
You can disable the button after an onclick event. The user user would have to reload the page to reclick. Otherwise you'll need some JS to re-enable after a timeout.

# assumes you use jQuery
link_to 'Toggle', toggle_path, :remote => true, :onclick => '
var e = $(this);
if(e.data("busy") == "yes"){
return false;
} else {
e.data("busy", "yes");
setTimeout(function(){
e.data("busy", "no");
}, 5000);
}
'.gsub("\n", '')
Check http://jsfiddle.net/u42gQ/1/

Related

Can I use ActionCable to refresh the page?

I've recently been trying to create a live-scoring system for squash matches. I've managed to use ActionCable with Rails 5 to auto-update the score on the page, but I'd like to know if it's possible to tell Rails to refresh the page if a certain condition is met.
For example, if the game has finished, a different page is shown to say that the players are having a break between games. I need the page to refresh completely for this to happen.
In my database the boolean 'break' is marked as true when a game ends, and then the view uses a conditional if/else statement to decide what to show.
The code I use to update the score is attached below, I was thinking something along the lines of if data.break == true then the page will automatically refresh.
// match_channel.js (app/assets/javascripts/channels/match_channel.js)
$(function() {
$('[data-channel-subscribe="match"]').each(function(index, element) {
var $element = $(element),
match_id = $element.data('match-id')
messageTemplate = $('[data-role="message-template"]');
App.cable.subscriptions.create(
{
channel: "MatchChannel",
match: match_id
},
{
received: function(data) {
var content = messageTemplate.children().clone(true, true);
content.find('[data-role="player_score"]').text(data.player_score);
content.find('[data-role="opponent_score"]').text(data.opponent_score);
content.find('[data-role="server_name"]').text(data.server_name);
content.find('[data-role="side"]').text(data.side);
$element.append(content);
}
}
);
});
});
I don't know if this sort of thing is possible, and I'm not much good at anything Javascript related so I'd appreciate any help on this.
Thanks.
Reloading the current page is relatively straightforward. If you are using Turbolinks, you can use Turbolinks.visit(location.toString()) to trigger a revisit to the current page. If you aren't using Turbolinks, use location.reload(). So, your received function might look like:
received: function(data) {
if (data.break) {
return location.reload();
// or...
// return Turbolinks.visit(location.toString());
}
// your DOM updates
}
Either way is the equivalent to the user hitting the reload button, so it will trigger another GET, which calls your controller and re-renders the view.

MEAN / AngularJS app check if object already posted

I have thig angularJS frontend and I use express, node and mongo on the backend.
My situation looks like:
//my data to push on server
$scope.things = [{title:"title", other proprieties}, {title:"title", other proprieties}, {title:"title", other proprieties}]
$scope.update = function() {
$scope.things.forEach(function(t) {
Thing.create({
title: t.title,
//other values here
}, function() {
console.log('Thing added');
})
})
};
//where Thing.create its just an $http.post factory
The HTML part looks like:
//html part
<button ng-click="update()">Update Thing</button>
Then on the same page the user has the ability to change the $scope.things and my problem is that when I call update() again all the things are posted twice because literally thats what I'm doing.
Can someone explain me how to check if the 'thing' its already posted to the server just to update the values ($http.put) and if its not posted on server to $http.post.
Or maybe its other way to do this?
I see a few decisions to be made:
1) Should you send the request after the user clicks the "Update" button (like you're currently doing)? Or should you send the request when the user changes the Thing (using ngChange)?
2) If going with the button approach for (1), should you send a request for each Thing (like you're currently doing), or should you first check to see if the Thing has been updated/newly created on the front end.
3) How can you deal with the fact that some Thing's are newly created and others are simply updated? Multiple routes? If so, then how do you know which route to send the request to? Same route? How?
1
To me, the upside of using the "Update" button seems to be that it's clear to the user how it works. By clicking "Update" (and maybe seeing a flash message afterwards), the user knows (and gets visual feedback) that the Thing's have been updated.
The cost to using the "Update" button is that there might be unnecessary requests being made. Network communication is slow, so if you have a lot of Thing's, having a request being made for each Thing could be notably slow.
Ultimately, this seems to be a UX vs. speed decision to me. It depends on the situation and goals, but personally I'd lean towards the "Update" button.
2
The trade-off here seems to be between code simplicity and performance. The simpler solution would just be to make a request for each Thing regardless of whether it has been updated/newly created (for the Thing's that previously existed and haven't changed, no harm will be done - they simply won't get changed).
The more complex but more performant approach would be to keep track of whether or not a Thing has been updated/newly created. You could add a flag called dirty to Thing's to keep track of this.
When a user clicks to create a new Thing, the new Thing would be given a flag of dirty: true.
When you query to get all things from the database, they all should have dirty: false (whether or not you want to store the dirty property on the database or simply append it on the server/front end is up to you).
When a user changes an existing Thing, the dirty property would be set to true.
Then, using the dirty property you could only make requests for the Thing's that are dirty:
$scope.things.forEach(function(thing) {
if (thing.dirty) {
// make request
}
});
The right solution depends on the specifics of your situation, but I tend to err on the side of code simplicity over performance.
3
If you're using Mongoose, the default behavior is to add an _id field to created documents (it's also the default behavior as MongoDB itself as well). So if you haven't overridden this default behavior, and if you aren't explicitly preventing this _id field from being sent back to the client, it should exist for Thing's that have been previously created, thus allow you to distinguish them from newly created Thing's (because newly created Thing's won't have the _id field).
With this, you can conditionally call create or update like so:
$scope.things.forEach(function(thing) {
if (thing._id) {
Thing.update(thing._id, thing);
}
else {
Thing.create(thing);
}
});
Alternatively, you could use a single route that performs "create or update" for you. You can do this by setting { upsert: true } in your update call.
In general, upsert will check to see if a document matches the query criteria... if there's a match, it updates it, if not, it creates it. In your situation, you could probably use upsert in the context of Mongoose's findByIdAndUpdate like so:
Thing.findByIdAndUpdate(id, newThing, { upsert: true }, function(err, doc) {
...
});
See this SO post.
#Adam Zemer neatly addressed concerns I raised in a comment, however I disagree on some points.
Firstly, to answer the question of having an update button or not, you have to ask yourself. Is there any reason why the user would like to discard his changes and not save the work he did. If the answer is no, then it is clear to me that the update should not be place and here is why.
To avoid your user from loosing his work you would need to add confirmations if he attempts to change the page, or close his browser, etc. On the other if everything is continuously saved he has the peace of mind that his work is always saved and you dont have to implement anything to prevent him from loosing his work.
You reduce his workload, one less click for a task may seem insignificant but he might click it many time be sure to have his work save. Also, if its a recurrent tasks it will definitely improve his experience.
Performance wise and code readability wise, you do small requests and do not have to implement any complicated logic to do so. Simple ng-change on inputs.
To make it clear to him that his work is continuously save you can simply say somewhere all your changes are saved and change this to saving changes... when you make a request. For exemple uses, look at office online or google docs.
Then all you would have to do is use the upsert parameter on your mongoDB query to be able to create and update your things with a single request. Here is how your controller would look.
$scope.update = function(changedThing) { // Using the ng-change you send the thing itself in parammeter
var $scope.saving = true; // To display the saving... message
Thing.update({ // This service call your method that update with upsert
title: changedThing.title,
//other values here
}).then( // If you made an http request, I suppose it returns a promise.
function success() {
$scope.saving = false;
console.log('Thing added');
},
function error() {
//handle errors
})
};

Rails 4 - update value or data attribute in form from ajax and use this updated value in an if statement

I have a Rails app in which Users can belong to one or many Teams. Users can create ideas for each team. I want the form for Ideas to look and behave slightly different depending on how many teams the user is a member of. If a user isn't a member of any teams this message will be shown: You need to create a team first, before the user can create any ideas.
The issue I'm having is that I create Teams with Ajax. In the ideas form I currently have conditions like this:
- if current_user.teams.count == 0
You need to create a team first
- else
[..]
Since I create the teams using Ajax, this message is being displayed even when the user have created his / her first team. Unless he / she reloads the page of course, then it works. But I want to have a seamless experience.
So this condition:
- if current_user.teams.count == 0
Needs to be changed into something that I can access and update from a js.erb file, and I'm not quite sure how I can achieve that.
I was thinking of possibly using a hidden_field_tag, that I can update in Ajax. But I'm not sure how my if statement in the view would look then. I have tried the following without success (I use HAML):
= hidden_field_tag "team_count", current_user.teams.count, id: :team_count
:javascript
if ($("#team_count").val > 1) {
[...]
} else {
[...]
}
Any ideas on what I should do instead?
The easiest way to pass data from Rails to your front end is via a route (accessed with ajax).
If you're also creating the team via an ajax call, I'd make a function that checks current teams and updates your view accordingly as part of the team creation callback.
Something like:
function updateDom(){
$('#myForm').text('On a team');
};
function checkTeam(){
$.get('/numTeams', function(data){
if (data.teams > 1){
updateDom();
}
})
};
function createTeam(args){
$.post('/createTeam', data, function(res){
checkTeam();
})
};
So, when a team is created, it'll check for the team and update the DOM accordingly.
You could also have js constantly check if the team is different and update accordingly with
var formText = window.setInterval(checkTeam, 1000);
The bottom line is that anything dynamic will be handled by js, not by rails (besides totally refreshing the page to get new information populated on load).
Bouncing on my idea of local storage...
If you only care about this for current_user, then you could do something like this...
EDIT : counting teams rather than just checking for any
EDIT 2 : why even use localStorage ? Just javascript variables would do.
EDIT 3 : Actually, local storage will preserve consistency across tabs !
In your view.html.erb
...
<%= if current_user.has_teams %>
<script>
/* Using local storage : */
localStorage.setItem('nb_teams', <%= current_user.teams.count %>);
/* ...Hey ! We can just add a javascript variable */
var num_teams = <%= current_user.teams.count %>
</script>
<% end %>
....
In the javascript of your form
/* Local storage only : */
num_teams = parseInt(localStorage.getItem('nb_teams'))
if( num_teams === 0){
alert("You don't have any team !");
} else if (num_teams ===1) {
// Normal case
} else if (num_teams >= 2){
alert("Please select a team")
}
In the javascript of your AJAX team creation
function createTeam(){
....
$.post(....)
.success( function(){
/* Local storage */
num_teams = parseInt(localStorage.getItem('nb_teams'))
localStorage.setItem('nb_teams', num_teams+1);
/* Plain old javascript */
num_teams ++;
})
}

Alternatives to using a submit button with jQuery

Currently I am using a ajax post back to pass data to a my controllers, wherein I do my magic from there. The problem I am having right now is that we have removed a submit button and instead attached a handler using .keyup(), on a smaller database this works great! However when moving it to a larger database, as you might of guessed, it causes all sorts of issues, from delayed responses to crashes etc. So it won't work. My question to stack overflow would but this: What's a user-friendly version to submit a form?
Since the front end user could potentially be searching for single character values (e.g 6 as in userid 6) I can't limit it to a minimum character submission. We don't want a delay timer as that could back fire in a couple different ways. so basically I've ruled out .delay(), .blur(), and .keyup().
If your requirement is to not use a submit button but rather process the input as the user types then I have used solutions like this in the past.
First define a function to manage the delay, something like this:
var inputDelay = (function () {
var timer = 0;
return function (callback, ms) {
clearTimeout(timer);
timer = setTimeout(callback, ms);
};
})();
Then for your actual ajax-call do something like this in your onkeyup event:
inputDelay(function () {
//AJAX-call goes here
}, 400)
Please note that this is not a universal solution for handling the order of responses from the server though. You might very well get several AJAX requests sent to the server and this code does nothing to make sure you only handle the latest call.
The best way to handle your situation would be to use a form, complete with a submit button. You would then add your event listener to the submit event of the form, cancel the post-back, and then do your processing. This would handle both the user clicking the submit button as well as pressing the enter key from inside the form.
I had the same issue as you.. What I did is the following:
I used on keyup.. and I fire the search if the user stayed still for .. say 300 millisecond or 500 millisecond..
While the user is typing I clear and reset the setTimeout().. so if the user stayed still for the timeout time the search will fire.
Additionally you can take the reference of the xHR object and cancel it if another request was to take place..
Do I understand your question correctly? if you need a written example I can write you a fiddle.

Continually check for an Oracle record at page load

I basically have a page that when loads, reads an Oracle SQL table for a specific record id that may not currently exist at the point as it may take up to a minute to insert this specific record into the table.
Based on this, I need a means of showing a "Loading Image" while it waits for the record to exist, so has to wait. Once it does, I want to remove the loading image and present the user with the record details. I am using Oracle Application Express 4.2 for this.
My question is not so much the loading/hiding of the image but how to continually check for the record within the Oracle table, during page load.
Either I receive the record successfully and then hide the image or say after 1 minute, I dismiss the checking of the record and present the user with a message indicating that no record was found.
Sorry for my english. I will try help you.
Make your "Loading image" always visible on the page. There is no need to show it on load, you only need to hide it at proper moment.
Add Application Process to your application. Name it for example "GET_MY_ROW". Process must check your event, and return some flag, for example 1 or 0.
Example:
declare
l_cnt number;
begin
select count(*)
into l_cnt
from table1 t
where id = 12345;
if l_cnt > 0 then
htp.p(1);
else
htp.p(0);
end if;
end;
3.3 Add javascript code as page load event (for example by Dynamic Actions):
Javascript code:
var myInterval = setInteral(function {
var get = new htmldb_Get(null,$v('pFlowId'),'APPLICATION_PROCESS=GET_MY_ROW',$v('pFlowStepId'));
get.GetAsync(function(pRequest) {
if (pRequest.readyState == 4) {
if (pRequest.responseText == 1) {
alert('Record loaded successfully');
// add function call, hiding your "Loading image" here
clearInterval(myInterval);
}
};
});
get = null;
}, 5000); //check every 5 seconds
setTimeout(function() {
alert('Sorry, no record was found. Try again later.');
clearInterval(myInterval);
}, 60000); // fail after 1 minute
Since NoGotnu already answered, I'll put this here:
Is there any reason for the procedure to be called through a job? Is it the only way to create the required record? Is the job called anywhere else? Why not call the procedure directly when the required page has been submitted and show the loading icon there? When it finishes, the user knows it has finished. That would involve a lot less fiddling around as you can make apex show a processing graphic on page submit. You could then just inform the user on the other page that the process has not been ran yet and they'd have to do that first.
Secondly, while NoGotnu's answer will work, I'd like to point out that in apex 4.2 you should use the apex.server namespace instead of the never documented htmldb_Get construction. apex.server.process is a clean implementation of the jQuery ajax setup.
NoGotnu's code translated:
apex.server.process( "GET_MY_ROW"
, null
, { dataType: text
, success: function(pData){
if (pData == 1) {
clearInterval(myInterval);
alert('Record loaded successfully');
};
}
}
);
The call doesn't really need to be async though, but ok.
Another option would be to implement a "long poll" instead of firing the ajax event every 5 seconds. A long poll will just initiate a call to the server and wait for a response. As long as the server is busy, the client will wait. To achieve this you could use dbms_alert, as suggested in Waiting for a submitted job to finish in Oracle PL/SQL?
You'd signal an alert in the plsql code of the job, and in the ondemand process code register an interest in the alert and use waitone/any with a 60 second timeout. Presto long poll.

Categories

Resources