Call javascript function onclick in Rails - javascript

I have the following code in one of my views:
<button type="button" onclick="analyze();">Analyze</button>
And, the .js.coffee file for the corresponding controller contains
analyze = ->
$.ajax
type: "GET"
url: "http://localhost:3000/api/logs"
success: (data) ->
Analytics.doAnalysis data
return;
return;
But, when I click the button, I get an error in console saying Uncaught ReferenceError: analyze is not defined. I think my onclick is not correctly defined. What is the recommended way of doing this?

I am new to web development
Since you're new, let me give you some ideas on how to improve your code:
Unobtrusive JS (UJS)
This tutorial explains it very well
Rails convention is geared towards "unobtrusive" javascript functionality. This means you can assign events to elements on your page without having to reference those elements directly (don't use inline onclick etc)
This is best described in the same way as CSS - using inline CSS is ridiculously laborious & seen as bad practice (it's not DRY). A much better way is to have a central stylesheet, which you can then use to style elements on the page.
Unobtrusive Javascript works in the same way - keep everything in files you call at runtime:
#app/assets/javascripts/application.js
$(".element").on("click", function(){
...
});
The importance of this cannot be stated enough - having unobtrusive JS is one of the best programming patterns you can apply; it not only DRIES up your code, but also ensures future development can be kept modular (a gold standard in development)
Ajax
Since you're using ajax, you may wish to use the Rails UJS Ajax functionality, which basically means this:
<%= button_to "Analyze", analytics_api_path, id: "analyze", remote: true %>
The remote: true option of this calls the Rails UJS driver's ajax functionality - basically setting up an ajax call without you having to write any code. The caveats of this are that you need to handle your own ajax:success process, and you'll need to point the element to the correct href
I would do this:
#app/assets/javascripts/application.js.coffee
$("#analyze").on "ajax:success", (data, status, xhr) ->
Analytics.doAnalysis data

Change this:
analyze = ->
To this:
window.analyze = ->
Functions in coffeescript are not global by default.

Related

What's the best practices for frontend in Rails?

i'm a front-end dev currently working in a team on a big Ruby on Rails project. It is not SPA. All the page rendering is done on server side.
Project had dozens of views with logic like
if true render this partial else render another partial
I try to follow DRY principles, i learned a lot from arkency blog, and tried to implement as much reusable components as possible.
But I feel the lack the of things like componentDidMount from React.
So my question is: is it ok to write inline javascript (i.e. add logic) in rails partials?
And what's the best way to write Javascript for maintainable Rails apps?
is it ok to write inline javascript (i.e. add logic) in rails partials?
My answer is no. It's not maintainable and it's ugly.
And what's the best way to write Javascript for maintainable Rails apps?
See below
You are going to get a lot of opinion based answers. There really is not true answer to this problem. My answer is 1) opinion-based and 2) no where near perfect.
That being said, I'd like to offer my own opinion. I have seen and built large and small sized Rails applications over the past few years (apps of 10 js files and apps of 100+), and I have been very dissatisfied with the organization of JavaScript in those apps and the apps I've seen across GitHub. I've seen countless JavaScript files full of unorganized and disassociated code. That doesn't seem very Rails-like to me. Over the past few months, I've been trying to find the solution to this, and there is one solution I found that gets the closest to a well organized JavaScript codebase on a Rails app. And I think it stays true to some Rails ideals. One downside to this method is it litters the global scope...I'd love to here from a JS developer on how to fix this.
Here is the Medium post:
https://medium.com/#cblavier/rails-with-no-js-framework-26d2d1646cd#.36zis335e
I have made a few tweaks to this, because sometimes you need to share code, for example, code that powers a shared form for your users. However, I'd like to give all credit to #cblavier. So please please take the time to read his post, because it has a ton of great information, and I won't go into complete detail below.
Requirements: Coffeescript, Turbolinks, and jQuery
# app/helpers/application_helper.rb
def js_class_name
action = case action_name
when 'create' then 'New'
when 'update' then 'Edit'
else action_name
end.camelize
"Views.#{controller_name.camelize}.#{action}View"
end
For the above helper, you will need to account for namespaced controllers if you app has namespaced controllers. That should be fairly easy though. I think the below would do the trick.
"Views.#{controller_path.camelize.gsub('::', '.')}.#{action}View"
Alright, now you want to add that to the <body> tag in your layout.
<body data-class-name="<%= js_class_name %>">
Time for the javascript!
# initializer.coffee
pageLoad = ->
className = $('body').attr('data-class-name')
initializePage(className)
initializePageBase(className)
initializePage = (className) ->
window.applicationView = try
eval("new #{className}()")
catch error
new Views.ApplicationView()
window.applicationView.render()
initializePageBase = (className) ->
modules = className.split('.')
modules.splice(modules.length - 1, 1)
window.baseView = try
eval("new #{modules.join('.')}.BaseView")
window.baseView.render() unless window.baseView is undefined
$(document).on 'turbolinks:load', pageLoad # turbolinks:load is master branch of turbolinks, if you are using older version, it's page:load
$(document).on 'page:before-change', ->
window.applicationView.cleanup()
true
$(document).on 'page:restore', ->
window.applicationView.cleanup()
pageLoad()
true
# app/assets/javascripts/views/application_view.coffee
window.Views ||= {}
class Views.ApplicationView
render: ->
# pretty much global JS code can be initialized here. It's nice
# to keep the render() method clean though. Like this:
#setupElements()
setupElements: ->
$('[data-toggle=tooltip]').tooltip() # just an example
cleanup: ->
Now that you have those setup, it's time to start adding your page JavaScript. Here is just a example of one for the page. users_controller#show
Views.Users ||= {}
class Views.Users.ShowView extends Views.ApplicationView
constructor: ->
# find and cache DOM objects, etc
# ex:
#someButton = $('[data-behavior=expand-user-info]')
render: ->
super() # this is important. It calls render() on ApplicationView
# example stuff
#bindEventListeners()
bindEventListeners: ->
t = this
#someButton.on 'click', ->
t.expandUserInfo()
expandUserInfo: ->
alert('woohoo!')
cleanup: ->
super()
If you noticed earlier, in the initializer.coffee method, we called a method, initializePageBase(). When I was using the structure from that Medium post, I ran into an issue where I needed the same javascript on both the edit and new views. That initializePageBase() is the first step to solving it. It will look for a BaseView class. Here is an example:
# app/assets/javascripts/views/users/base_view.coffee
Views.Users ||= {}
class Views.Users.BaseView # you don't need to extend ApplicationView here, because we are already initializing it.
render: ->
# code
One way to solve your problem is to use the react.rb gem (and the reactive_rails_generator to do the installation)
This will allow you to write your views in react style you are used to.
Even though its not a single page app react.rb can simply as your templating langugage. If you have some interactive on page features you are good to go.
There is also a reactive-record gem which might be worth looking into.

Returning HTML to AJAX Rails calls

After reading David Heinemeier Hansson's blog post about server-generated javascript I decided to review the way I approach making AJAX calls in my Rails applications. What David suggests is to create a .js.erb template, which is just javascript embedded with ruby code generated on the server, and not to do any DOM manipulation in the client-side javascript.
Another approach is of course to simply do everything on the client-side, and (for example) return a JSON object representing the updated object from the server and use javascript to do all DOM manipulation.
I dislike the first approach for two reasons:
1) I use HAML and Coffeescript in my application, and feel that by using vanilla javascript and ERB would uncecessarily bloat my code-base with code of a different language (maybe it's possible to create .coffee.haml templates rather than js.erb, I don't know)
2) I really don't like the idea of 'littering' my view folder with what is essentially javascript files, with a little bit of ruby embedded.
The second approach, as David talks about in his blog post, relies very heavily on client-side javascript, which could lead to bloated client-side javascript code, and possibly the need for client-side templates, which in worst case scenarios could mean almost doubling the number of templates.
The approach I decided to go for (and want to ask whether or not is completely stupid way to go about this) is the following:
1) Set the remote: true flag to make links and forms utilize AJAX to post to the server.
2) In my controller, handle everything as html, and simply render without layout should the request be an AJAX request: render partial: '<partial-name>', layout: false if request.xhr?. This simply returns the HTML of the partial, with the ruby code evaluated.
3) In an asset javascript file (for instance <partial-name>.js.coffee) listen to ajax:success and append the HTML from the response.
I like this approach because (in my rather simple application) this allows me to keep all my code in HAML/Coffeescript, and avoids any javascript templates.
I realize this problem might take on a different character should the complexity of the application grow, but I still feel that it's a valid question: is this a bad way to go about implementing an AJAX-based architecture for a Rails application (and if so, why? i.e. is there a reason why returning HTML instead of JSON from an AJAX call is a bad idea?) or is this something I should continue to utilize?
Thank you :-)
Your approach seems to me quite accurate. However I would change 1 pt. Instead of using remote: true, I would use jQuery ajax call directly.
$(function(){
$('#some_link').click(function() {
$.ajax({
// some parameters here
})
.done(function(data){
$('div').html(data);
});
});
});
if you use remote: true it sends a js request instead of html request.
then when the controller method is executed, it looks for the js.erb or js.haml file which has the same name as the controller action that just finished executing.
in this file you can write 'js' code to perform any action that needs to be done after the controller action has completed executing like changing a partial or updating view, etc.
if you have a function in javascript asset file, you can call that function as well.

Is it correct to have files that mix Javascript and ruby? ie. (.js.erb)?

I've recently read that embedding ruby inside JavaScript is not a good idea.
However, in books such as David Heinemeier Hansson's Agile Web Development with Rails, that's exactly what it does.
If embedding ruby with JS is NOT a good a idea, then what's the best practice for such case?
Given something as simple as this: (jQuery + ruby)
posts_controller
def create
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to(#post, :notice => 'Post was successfully created.') }
format.js #will use this response to process ajax
else
format.html { render :action => "new" }
end
end
end
create.js.erb
$tr = $('<tr>');
$td1 = $('<td>').text('<%= #post.title %>');
$td2 = $('<td>').text('<%= #post.content %>');
$tr.append($td1, $td2);
$('table tbody').append($tr);
How should it be refactored to follow the "best practice" of not embedding ruby with JS?(if that's the case)
I really need to be enlightened on this, and maybe get the concepts right because I have read that rails 3.1 will separate JS from Ruby completely through assets ?(Is this correct?)
Thanks !
RJS templates were essentially the way things were done for a long time, which is why you still see such a prevalence of the technique in tutorials. That being said, as you've noticed, they're on the way out, and with good reason.
I'm sure there are many reasons why RJS templates are a very bad thing, but a big one is how tightly they couple your view JS to your view HTML. Many problems arise from this, a few being:
Lack of flexibility.
Using your bit of code as an example. What if you wanted to be able to create posts from a different view? And have a different effect? Maybe there's no <table> on the page at all and you just want to pop up a "success" message? Etc.
What if you just wanted a data representation of your posts, but your javascript templates were written to manipulate HTML?
How do you handle all this in one create.js.erb, which is tightly coupled to, most likely, the posts new.html.erb template?
Complexity.
RJS templates operate in somewhat of a vacuum. Generated on server side, their execution is not bound to DOM events. This makes it tricky to do things like, say, update a <form> on a page after an object is being created, as you have no frame of reference to select the appropriate <form> in the JS template (e.g. <form id="new_post_123">). There are workarounds for this, but they're more convoluted than they need to be.
By binding to a form client-side and dealing with the result, this problem is eliminated. You don't need to find the proper form to update after a response.
With RJS you have no such binding, and you're forced to use the known structure of the HTML to retrieve and manipulate the appropraite elements. One example of unnecessary complexity.
Regarding your question:
I really need to be enlightened on this, and maybe get the concepts right because I have read that rails 3.1 will separate JS from Ruby completely through assets ?(Is this correct?)
This is essentially true. The asset pipeline, while cool, doesn't offer anything brand new. Asset frameworks like Sprockets and Sass, and the workflows they enable, have been around for a while. The Rails 3.1 asset pipeline just brings them into standard practice. It's more a matter of perspective, as DHH talked about in his recent RailsConf keynote. By bringing greater organization to these assets, js files in particular, it makes them feel more like first-class citizens, so to speak, deserving of as much attention as your back-end code. As opposed to the "junk drawer" feel of public/javascripts.
As for the implementation:
You might do it something like this (although untested and a bit simplified, e.g. in Rails 3 you might use responders for this rather than a respond_to block:
# as you have, but returning the object as data which can be handled client-side,
# rather than RJS generated by the server
def create
#post = Post.new(params[:post])
respond_to do |format|
if #post.save
format.js { render :json => #post }
else
# ...
end
end
end
// Then in your client side JS, a handler for your remote form.
$(function(){
$('#your-create-form').bind('ajax:success', function(data, status, xhr) {
// simply repeating what you had in the template, replacing the erb with
// attributes from the returned json
$tr = $('<tr>');
$td1 = $('<td>').text(data.title);
$td2 = $('<td>').text(data.content);
$tr.append($td1, $td2);
$('table tbody').append($tr);
});
}
If you embed dynamic data in JavaScript files, then you often have to give it caching rules suitable for content that can change frequently.
If you need to process dynamic content with JS then it is usually better to have a static, cacheable, reusable script that gets data from elsewhere.
In this specific example, although I'm not sure since there isn't a great deal of context, it looks like you would be better off generating HTML directly instead of generating JS that generates HTML.
I'm sure you can find this question asked by beginning PHP devs, Perl, ASP and whatever other server-side languages there are out there. There don't seem to be too many cases where you'd want to have your javascript code inline.. the only one might be where you're customizing it to a particular situation, but off the top of my head I can't think of any situations where you'd need to customize it like that.
Peformance-wise, if you have a lot of javascript and you include it all in your output, you bloat your page. A 1k page will turn into a >100k page if you're adding a big application in. And, along with that, it will likely be more difficult to cache that information since you're probably customizing the output just a little bit if the user is logged in, or if there's a special ad out that day, etc. You're much better off splitting it off into its own file so that you can "minimize" it (http://en.wikipedia.org/wiki/Minification_%28programming%29) as well.
From a developer to a developer, I can give you horror stories of having javascript (and html) embedded in both PHP and Smarty templates. Be nice to whoever you're working with and split all the languages out into their own files. Let's say you're working with Bob and he's a great javascript guy.. but doesn't know a thing about the application and how it spits out its code. Having javascript smattered in the erb's is going to be frustrating due to the lack of a single point of truth for the application - if he wants to tweak something, he's going to be looking through all your erb's to figure out where exactly he has to put it or where was it originally. And do you really want someone who knows little about your backend's structure to be poking around through those files?
There's a couple of cases though where you'd want to put bootstrap data in.. if you've got a lot of data to inject to the page on startup, having that assigned to a few variables when the page starts is more performant than going out and making an ajax request on every page load.
Please be nice to us front-end guys and unless you've got a real good reason.. keep the JS in separate files :)
Edit: Here's an example of doing what you want (I think), without really using templated javascript:
app.js:
postUpdater = function (data) {
$tr = $('<tr>');
$td1 = $('<td>').text(data.title);
$td2 = $('<td>').text(data.content);
$tr.append($td1, $td2);
$('table tbody').append($tr);
};
$("#save_button").click(function () {
$.post('/save/ur/', {the:data}, postUpdater);
});
In ruby, you'll want to just render the #post to json.. I think it's literally #post.to_json, but you may have to require 'json' in order to get it to work. This might get you there faster though: output formated json with rails 3 (I'm not really a ruby guy.. we just use it at the company I work for so of course I've been picking it up)
Now, as for a theoretical on why not to just output a templated javascript file. Let's say you've got a whole bunch of stuff that needs to happen when you save that javascript file. That means you've got to output a whole lot of stuff on that ajax request and execute it. You'd be pushing a larger file over the internet (a tiny bit slower) and possibly having to 'eval' a response (eval is evil). So, that's one bad thing. Another possibly bad thing is that you lose access to where it was you called the save from. So, if I've got this view..
new PostViewer({
savePost: function () {
var self = this;
$.post('/save/url/', {the:data}, function (response) {
self.trigger('saveSuccessful', response);
});
}
});
When the savePost gets called it will be more difficult (without possibly introducing numerous hacks) to simply tell the view "Hey, the save was successful!".
Hopefully that's a bit more on target for what you were looking for.
yes, you can write but that file extension should be
".js.erb"

What is the best way to organize JS code in webapps where the main "meat" is still server side?

When building webapps with MVC web framworks like Django, Kohana, Rails and the like, I put together the application without JS-driven components initially, and then add them afterwards as "improvements" to the UI.
This approach leads to non-intrusive JS, but I don't have a good "standard" way of how to go about organizing the JS work. Most of the JS I write in apps like these are 10-30 line JQuery snippets that hook into some very specific part of the UI.
So far I often end up inlining these things together with the part of the UI they manage. This makes me feel dirty, I'd like to keep the JS code as organized as the python / php / ruby code, I'd like for it to be testable and I'd like for it to be reusable.
What is the best way to go about organizing JS code in a setup like this, where we're not building a full-blown JS client app, and the main meat is still server side?
I am also very interested in what other people have to say about this. The approach I've taken is to use object literal notation to store the bulk of the function, and store these in one file included on all pages (the library)
uiHelper = {
inputDefault:function(defaulttext){
// function to swap default text into input elements
},
loadSubSection:function(url){
// loads new page using ajax instead of refreshing page
},
makeSortable:function(){
// apply jQuery UI sortable properties to list and remove non javascript controls
}
}
Then I include a .js file on any page that needs to use the library that ties the elements on that page to the function in the library. I've tried to make each function as reuseable as possible and sometimes the event binding function on the page calls several of my library functions.
$(document).ready(function(){
$('#mybutton').live('click',uiHelper.loadSubSection);
//more complicated helper
$('#myotherbutton').live('click',function(){
uiHelper.doThisThing;
uiHelper.andThisThing;
});
});
edit: using jsDoc http://jsdoc.sourceforge.net/ notation for commenting for these functions can produce documentation for the 'library' and helps keep your code easy to read (functions split by comments).
The following question is along similar lines to your own - you should check it out...
Commonly accepted best practices around code organization in JavaScript
When dealing with JS code, you should first analyze whether it will be used right away when the page loads. If it's not used right away (meaning the user must do something to invoke it) you should package this into a JS file and include it later so the load time is perceived faster for the user. This means that anything that the user will sees should go first and JS related to the functionality should be imported near the end of the file.
Download this tool to analyze your website: http://getfirebug.com/
If the JS code is small enough, it should just be inline with the HTML.
Hope that helps a bit.
For quick little user interface things like that I put everything into a single javascript file that I include on every page. Then in the javascript file I check what exists on the page and run code accordingly. I might have this in UIMagic.js for example. I have jQuery, so excuse those jQuery-isms if they aren't familiar to you.
function setupMenuHover() {
if ($("li.menu").length) { // The page has a menu
$("li.menu").hover(function() { ... }, function() { ... });
}
}
$(setupMenuHover);
function setupFacebookWizbang() {
if (typeof FB != "undefined") { // The page has Facebook's Javascript API
...
}
}
$(setupFacebookWizbang);
I've found this to be a sane enough approach.
My preferred method is to store inline javascript in it's own file (so that I can edit it easily with syntax highlighting etc.), and then include it on the page by loading the contents directly:
'<script type="text/javascript">'+open('~/js/page-inline.js').read()+'</script>'
This may not perform well though, unless your templating library can cache this sort of thing.
With Django you might be able to just include the js file:
<script type="text/javascript">
{% include "js/page-inline.js" %}
</script>
Not sure if that caches the output.
If you are still worried about being 'dirty', then you could check out the following projects, which try to bridge the server/client side language mismatch:
http://pyjs.org/ (Python generating JavaScript)
http://code.google.com/webtoolkit/ (Java generating JavaScript)
http://nodejs.org/ (JavaScript all the way!)

Ruby on Rails with Unobtrusive JavaScript - Managing URLs

I'm using a modular system of JavaScript files when working in Rails - basically each view will have its own module in a .js file. My issue comes when I need a dynamic, Rails generated string within my JavaScript, for example translation strings and URLs.
Translations are nicely solved using babilu but I'm still stuck on the generation of URLs. I could write something that looked at the routes in the application and generate JavaScript methods which I could pass stuff like IDs of objects.
An alternative would be to pass in the already-generated URL to any functions I was calling, which sounds messy but could be the most flexible alternative.
I don't know that there's any truly pleasing way to do this, but one possibility is to have your server-side code write a small <script> block into the page to declare some variables that your packaged Javascript can discover and use.
<script>
var pageGlobals = {
interestingURL: <% ... url %>,
...
};
</script>
I've done this to keep track of things like image subdirectories that are determined by customer "syndicate" affiliation. The Javascript code just knows that whenever it needs that for a URL, it can just go look in a global object and pick out a standardized variable.
In my experience there tend to be only a small number of such things to communicate to the canned Javascript, so the <script> block tends not to get out of hand. I've buried mine in a page template so I don't even have to think about it with new pages.
Oldish question, but here's another way. The HTML 5 spec allows for custom data- attributes.
In your view:
<button id="myButton" data-url="<%= my_resource_path %>">Click me</button>
Then in your packaged js:
var myurl = $("#myButton").data("url");
See here also.
I don't like this either. The ideal solution to me would be javascript templates. Imagine in the .js file you could do:
var users_path = '<%= users_path %>';
But that would mean the .js files would have to be regenerated in every request (well, one could use caching just like with rails html).
Anyway, what you can also do is put the dynamic stuff in data- attributes. So you can do for example
<%= select_tag :select_something, select_options, 'data-url' => users_url %>
And then read that attribute out in the javascript file. I prefer this over the solution suggested by Pointy.
Edit: Well actually someone implemented the dynamic .js file idea. Seems straight forward enough, just create a javascripts controller and link to its actions via javascript_include_tag: dynamic javascript files

Categories

Resources