1

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?

Anders
  • 2,903
  • 7
  • 58
  • 114
  • Just wrap your `You need to create a team first` in a `div` with a certain `id` and in your js.erb call `$('#').hide()` – AbM Apr 20 '15 at 03:03
  • @AbM I want the idea form to have some default states based on conditions in my model and controller, independent of creating new teams. I'm not sure that there is a good way to pass this kind of information to a JS file. In my form I for example also have this condition: `current_user.teams.count > 1 ...`, and I hide/show content based on the result of this condition. Do you know If I can pass and access the value of ` current_user.teams.count` to a js.coffee file? – Anders Apr 21 '15 at 20:04
  • What about using local storage to store a boolean hasATeam that you could check ? – Cyril Duchon-Doris Apr 21 '15 at 20:29
  • The `update.js.erb` is rendered as a response to your `update` action, so you do have access to `current_user.teams.count` in it. – AbM Apr 21 '15 at 21:05
  • @AbM mm, thought I could use a `js.erb` file. The issue is that this form can be presented pretty much anywhere in the whole app since it's placed inside a bootstrap modal window. I couldn't achieve to use `respond_to` and render a specific js.erb file in for example `users/show`. – Anders Apr 21 '15 at 21:16
  • I am assuming the "idea" form is a modal that is hidden, to also create the ideas using ajax? So it is rendered only on first page fetch, correct? – nathanvda Apr 23 '15 at 08:07
  • Ok, imho a bit quick to award the bounty. So now you have doubled data. Each web-page will count locally the nr of teams created, while there is a canonical source of truth: your database. Which you can easily query each time you open the modal without any apparent delay for the user. What if a user is assigned a team by another team-member? – nathanvda Apr 23 '15 at 10:05

2 Answers2

3

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).

xavdid
  • 5,092
  • 3
  • 20
  • 32
  • Thanks! Think your solution would be nice. As noted in my comment above: "[...] thought I could use a js.erb file. The issue is that this form can be presented pretty much anywhere in the whole app since it's placed inside a bootstrap modal window. I couldn't achieve to use respond_to and render a specific js.erb file in for example users/show." So it's not really tied to a specific request / route. Do you know if it's possible to for example render `/ideas/new.js.erb` from `users/show`? – Anders Apr 21 '15 at 21:20
  • Yep! You can use the method described here: http://stackoverflow.com/questions/23055056/rails-render-a-js-erb-from-another-controller to render a .js.erb anywhere. – xavdid Apr 21 '15 at 21:43
0

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 ++;
  })
}
Cyril Duchon-Doris
  • 12,964
  • 9
  • 77
  • 164
  • Quickly tried you approach, ran into one issue though. Setting ` localStorage.setItem('user_teams', current_user.teams.count);` inside a script tag resulted in a `NaN` for `localStorage.getItem('user_teams')`. I also tried: `localStorage.setItem('user_teams', '<%= current_user.teams.count.html_safe %>');`, but then I didn't get the value of `current_user.teams.count`. Any ideas on how I can embed ruby inside the JS tag? – Anders Apr 21 '15 at 21:13
  • Oh sorry, just realised I had forgotten the `<%= .. %>`. No need for string delimiters `'...'` or `html_safe`, or at least on my ruby/rails it works perfect without it, and I can see the value is indeed stored using Firefox debugger. – Cyril Duchon-Doris Apr 21 '15 at 23:09
  • Wait.... actually you don't even need localStorage ! Just a plain javascript variable would do ! – Cyril Duchon-Doris Apr 21 '15 at 23:24
  • Thanks, got it to work by using your approach. Think it's the simplest solution since I present this form on a lot of different views / requests, the form can be presented pretty much on every page since it's placed inside a Bootstrap modal window. Is there any security or other downsides with using this solution compared to the answer given by @Xavdidtheshadow? – Anders Apr 22 '15 at 10:17
  • 1
    The only downside I can think of, is if there are many users that are likely to modify the same data (instead of just the `current_user` modifying his own ideas). In this case, you may have desynchronized `nb_teams`, but you can eventually add checks to the related the controller so it shouldn't be an issue. Otherwise, you are protected by [JavaScript same-origin policy¨](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy). Another advantage compared to @Xavdidtheshadow is that it will be more reactive, since there are no GET requests sent to check the number of teams. – Cyril Duchon-Doris Apr 22 '15 at 11:24
  • I personally do not like this idea at all: I try to avoid duplication at all costs, and in this case it is really easy to avoid it. This only makes your code more complicated, and it does not work in all cases (e.g. users being assigned to teams by another user, or in another browser, in another device). – nathanvda Apr 23 '15 at 10:05
  • Maybe I wasn't clear, but I do mention at the beginning that this is only good if we assume only the current_user can modify his team. Now, local storage has the advantage over simple variables that it will preserve consistency across tabs&windows on the same browser (and yes actually it's very likely to happen). Then I think it's ok to assume the users will only use one browser for the same website (but remember it if you run compatibility tests on different browsers). In any case, I believe that even by chance or by malice, desynchronisation wouldn't cause a lot of harm here. – Cyril Duchon-Doris Apr 23 '15 at 11:19
  • @CyrilDD Yes, think it works for my requirement here. – Anders Apr 23 '15 at 19:12
  • @nathanvda ok, would love to see how you would do it anyway, if you wouldn't mind. Thanks! – Anders Apr 23 '15 at 19:12