Skip to main content
30 Javascript and CSS Asset Pipeline:

Sharing Data With Javascript

Episode 100 · January 12, 2016

Learn how to share data between Rails and Javascript without using AJAX

Javascript


Transcripts

This episode I want to talk about another method for sending data from your rails application to the JavaScript to be used. JavaScript runs more or less in a separate domain than your rails application. Your rails application just simply generates html that displays this page, and then your JavaScript runs, and it only has access to urls and the data that it returns, so in the past, in episode 96, which you should watch if you haven't already, that episode we talked about using an AJAX request to generate these in-app notifications. These notifications are loaded using the notifications.json url, and you'll be able to see those inside the json object that you give back, which is an array of objects. If we were to go into another browser, and create a forum post, then that user would be able to refresh the notifications url, and see that there's a new notification with the id of number 4. That actually needs to be requested continuously, and this url needs to be pinged over and over again in order for this to show up. Also notice that when the JavaScript runs, when the page reloads, it can sometimes take a second to load up the number of notifications, and then the UI will have to change a little bit to show that you have one new notification. If we go refresh this again you'll see there is none, and then it does the request, and then it's able to parse those. If that takes a little bit of time, then you're going to see your page jumping around, and that's something that can be kind of painful on your first experience. How can we get this information preloaded in the page, so that we can automatically set that up, and display the notification number immediately, no matter how long that AJAX request runs. Well, the way we're going to do that, is we're going to embed json into your html, and I'll show you that now.

If you've watched episode 96, this is the exact same code from that, except that I put a timeout for 1 second so that when the page loads, that little delay happens, and that's going to simulate network latency, that's going to be what you experience in production. You've probably seen Facebook's UI update like that as well. How can we go about preloading this so that it automatically shows that number when the page loads. Let's take a look at our application.html.erb, and let's look at that notification's html that we have on there. This is actually our notification stuff, we have the data-behavior that specifies that this anchor tag is actually part of the notification's JavaScript. If you don't remember in the previous episode we talked about data-behavior tags, and this basically denotes that this html tag and all of it's children will run with certain JavaScript behaviors, and that's just a way for you to decouple the JavaScript from the html tags specifically, and the CSS classes and id's. So it helps you really separate all of that responsibility out, which is good. But this is also very valuable because of those data tags for stuff like preloading notifications into your html. Rather than going and waiting for that round trip, we can actually embed the AJAX here, and maybe we had data notifications, and this is what embedded that JavaScript, or that json object basically. If we were able to embed json here, we would have our JavaScript to be able to look at this tag, pull out that data, and then immediately, when the page loads, show the notifications, and then later it can just go check for updates. Let's actually go do that, let's delete this, and let's go back to localhost:3000/notification.json, and grab all of this json, and then just paste it into this data-notifications tag. Now I'm going to change the quotes here, so that we don't get weird escaping, but that will go ahead and embed the json into the html. So now, if we refresh our page here, we can open up our JavaScript console, and we can just look for data-behavior=notifications, this should grab us back our list object, and it does, and here you can see the notifications, that means we can just call data("notifications") on that object, and we ill get an array of json objects back. This is all the code that we actually need in order to reference that json thats preloaded into the page. So when the page loads, we can go an render immediately. All we're going to have to do, is go into our notification.js.coffee, and let's comment out this code here so that when the page loads, we'll never display notifications. Here let's actually use that preloaded json that we have on the page, and let's just console.log @notifications.data("notifications"). This is that part that we're already doing in the console, so we just need to use that variable to access the embedded notifications. When our page loads, we should see that same array gets printed out, and it does. This is cool, we've already got that going, and that just means that we now have the array, and as it turns out, that's the exact same data object as we get back from the AJAX request. That means we can just simply call @handleSuccess with that data, and we should be able to refresh the page, and immediately, you're going to see that you get the JavaScript notifications rendering, and they of course will have to wait for the body and the CSS is parsed and the DOM is loaded, but the JavaScript will run immediately then afterwards, and you'll have the most minimal delays as possible for that.

It might be kind of surprising that we can literally reuse the handleSucess here from rendering after we get the json back, but that is all we really need to do. That means you could actually go through and do this, immediately upon page load, and then you can also use that code for setTimeout just to simulate your browser being slow. You can actually replace this with a setInterval, and you can set it up to check for new notifications every 5 seconds, for example, and then you would get sort of a simulated real time set of notifications. That would be all that you would have to change for that to preload them immediately on the page, and then to check for updates every 5 seconds. It's pretty cool, it allows you to really have it easy to work with notification system, and then the last piece that we have to do is to go back here and replace this hard-coded notification with the actual notifications that we get from generating that json. Let's go ahead and do that now. Let's pull in the json that we hard-coded before, and let's replace this with rendering that exact same template that our application uses for notifications. That's the notifications/index.json.jbuilder file. This is the file that you want to render from inside this other template, and you can actually simply do that by calling <%= render template: "notification/index", formats: [:json] %> otherwise it won't look for a partial, and here you say "notifications/index", and because this is an html request, you're actually going to need to specify formats as json in order to force it to render the json format. That's really all you're going to have to do template-wise, but remember, this json jbuilder file is rendering the @notifications variable that is set in the notifications controller. This is the unread notifications for the current user, and we're not going to be necessarily inside this controller all the time. This is the application template, so this is for every single page that is rendered, we're going to need this variable to be queried. Now it's going to be pretty quick, and so we're going to be able to do that to preload it if your servers are fast and scaled up to the point where this is going to be making sense to preload this information. So we would actually need to go into the

application_controller.rb

before_action :set_notifications, if: :user_signed_in? 

def set_notifications
    @notifications = Notifications.where(recipient: current_user).unread 
end 

That should be all we need to do in order to set those notifications up dynamically. If we inspect that notifications again, we'll see that it's automatically parsed into a json object because the data notifications is already handled that way, which is pretty nifty, and so our notification.js.coffee really just needs to stay exactly the same and keep this set interval. It might make sense to rename the setup then, and move this out, so you would want to set this up to be run, instead of then, we want to do it there, and then we'll also getNewNotifications() every 5 seconds instead. That should do it, and of course, getNewNotifications should be set to the instance method there, the @ method, and it probably makes sense to also do this inside the if statement. We'll check to make sure that that object exists before we do any of the work with it. These are really the two set up methods that we're going to have now, and then this will just be called every 5 seconds. That means that we should be able to go back to this page, and then go to our other user and say: Hello, and we should go back to this, and if we wait for about 5 seconds, we should now see that number change to 4, and it does.

That is the secondary method of sending data over to your JavaScript, though on the one hand, you have AJAX in order to pull data over directly from the JavaScript, and then you also have your html with extra data hidden inside of it, in order to give the html data to work with immediately. That's something that plays a lot of different purposes, there's a lot of different use cases for this. For example, if you want to make sure your notifications are rendered immediately, then it makes sense to potentially embed that inside the html, and the reason for that is because when you're doing this, sure you have an extra query, and sure you have to render an extra template, but you're actually saving an entire request to the server through the AJAX portion of the code, and that is going to save a whole lot more time, because that would have to also run the query, also render the template, but it has to do the entire stack of rails, so it has to go through and set up the connection, parse the request, prepare the request, do all of the rails stack, and here you're already doing that, so you're just including a little extra data that you know is going to be needed anyways. This is useful as well to send data initially over to react or ember or angular or any other front-end framework, or even just jQuery like I showed here, and that will allow your JavaScript to run instantaneously when the page loads, as opposed to waiting for a request afterwards and doing all of that stuff. It helps include or minimize the request overhead that you have to do in order to get your JavaScript up and running on the client side, and also this is useful for anytime that you're doing something with Stripe for example, so you'll notice that you'll put like a meta tag up here that will include your Stripe public key inside the meta tag, and then that's a way for us to send that data over to the Stripe JavaScript library in order to tokenize the credit card on check. You can use this for a bunch of things, for example, you could even include a meta tag up here that denotes weather the user is logged in or not, not it will enable your JavaScript to know weather or not it can perform certain functions, without having to make the request and see if it gets rejected or not, so it is a way to cut down some of the requests and things just to pass data into your JavaScript that it might need to know. This episode is definitely a little variation from the usual, a little more heavy JavaScript focused, but this is going to be the foundations sort of the react's screencasts we are getting into in the future, because we'll use the react rails gem probably to render the react stuff server side, and you're going to have pretty much the exact same functionality when you're doing that. This is a little prep for that, and those should be coming very soon. Hope you enjoyed this episode, I will talk to you next week. Peace

Transcript written by Miguel

Discussion


Fallback

Thank you for this video, very interesting way of autoloading new notifications.

However, polling sounds a bit "old school". I read a lot about Active Live and websockets with the upcoming release of rails 5. Wouldn't it be interesting for this case to share new notifications automagically to all users concerned by them as soon as they are created ? Or maybe it's too heavy ?

Fallback

Polling works just fine in a lot of cases unless there's an absolute _need_ to have realtime. It's rare for notifications to need to be truly instant. The other beauty of polling is that you can just write regular old Rails and JS code which you're already familiar with scaling. Probably 99% of the time, polling will do the trick just fine.

However, that's something we'll be talking about in the future. WebSockets are a whole lot more complicated to setup, manage, and maintain, so it will be a topic for a separate series (one that's coming soon!).


Fallback

Hi Chris, thanks for this great video. One thing: I think the call to "render" needs to be surrounded by a call to "escape_javascript"(http://api.rubyonrails.org/.... For instance:

<li class="nav-item btn-group" data-behavior="notifications" data-notifications="&lt;%= escape_javascript(render template: "notifications/index", formats: [:json]) %&gt;">

Or, shorter, using the alias "j":

<li class="nav-item btn-group" data-behavior="notifications" data-notifications="&lt;%=j render template: "notifications/index", formats: [:json] %&gt;">

Otherwise, a quote character in the generated JSON can break your HTML.

Fallback

That's actually what I first did, but discovered that the way I showed in the video output and parsed the JSON just fine. I didn't run into any issues with it, but if it does become a problem for anyone, escape_javascript and some additional JSON parsing in the JS should do the trick.


Fallback

Fallback

Why have you decided to use CoffeeScript here? To me it seems really hard to justify when features are incompatible with ECMA 6. This makes me a little nervous about relying on these lessons.

Fallback

do you don't know that coffescript compiles to javascript and coffeescript is limited by what javascript can do? if you don't like coffeescript just take the generated javascript and work with :) and if you don't know coffeescript yet you have to know that it will be more easier for you to transform your code from coffeescript to ECMA 6 than the "actual" javascript to ECMA 6 ! only when you know coffeescript well you will know how powerful and beautiful the language is. another thing is that so many people use that "language", and the community is large, so you will always find ways to switch if someday you want to... the only problem I can see here is if you don't have time to understand that "very easy to learn" language (and frankly I was always nervous about coffeescript before I learn it, so I can understand you)

Fallback

I know CoffeeScript and abandoned it for the reasons I outlined above. Sure, you can compile your CS to JS. But then why not just write JavaScript?

If I were you, I wouldn't be looking forward to dealing with conflicts with keywords like `for..in`, `class`, and even `super`. But to each their own.

Fallback

No particular reason. I have been using CoffeeScript before ECMA 6 and am familiar with it so it's easy to use. Once nice thing is it's easily translatable to JS as well for anyone not familiar with it.

And yes, ECMA 6 (and 7) solve pretty much everything there. I'll have to do an episode showing how to use ECMA 6 with Rails as well.


Fallback

Chris, is using setInterval good for our Rails app performance?

Fallback

It will add extra requests, but they are pretty simple, plus you can enable caching around the JSON response to make it fast.

Fallback

How to enable caching around the JSON response Chris?

Fallback

Check out the documentation here: https://github.com/rails/jb...

And my previous episode on fragment caching: https://gorails.com/episode...

Fallback

Fallback

Great as always. Something i noticed is when
setInterval is fired and if the drop down menu is open, it will make disappear its content. How could we prevent that ?


Fallback

Thank you for the video, Chris! I implemented the notification feature by watching the previous tutorial and it works great! But it's tedious to test in the browser because I have to be logged in as two different users. I tried writing Jasmine test and capybara test but I'm a bit stuck. It'd be awesome if you could cover testing for this feature, too. Thanks for the tutorials!!


Fallback

You just solved about three problems I knew I'd be running into in the next few weeks, and thereby completely justified my subscription for quite a while. Thanks, and keep it up! 😌

Fallback

Wow, that's awesome, I'm really glad it was helpful! :)


Fallback

Love the video like this!


Fallback

This using ActionCable could be interesting, ill try. What about you Chris?.

Fallback

You can use this approach still the exact same way with ActionCable if you want to preload some data in the view before the Websocket connection gets initialized.

For the most part, with websocket stuff, I imagine in most cases you will want to just display a loading spinner instead while the connection gets setup. They'll happily work together though with no problem. The only difference is that you change the mechanism to update the data from AJAX to Websockets.


Fallback

If I'm using active model serializer, the way to do this on the view is to use `@notifications.to_json` on the render?
thanks


Login or create an account to join the conversation.