I have an application which uses some Javascript for basic Ajax requests such as autocompletion and live search. For example I implemented live search in the following way; I spotted some potential issue and would like to talk with you about it, so to have a better code.
app/controllers/company_controller.rb
def livesearch
@companies = Company.search(params[:query])
render :partial => "companies", :locals => {:companies => @companies}
end
app/views/companies/_companies.html.haml
- if companies.empty?
None
- else
%table#company_list
%tr
%th Name
%th Description
%th Products
%th
= render companies
app/views/companies/_livesearch_box.html.haml
= content_for :scripts, "jlivesearch companies"
= form_tag "#", :autocomplete => :off, :remote => true do
%span.light
Search:
= text_field_tag :search
:javascript
$('#search').livesearch({
searchCallback: update_listed_companies,
queryDelay: 200,
innerText: "Search companies"
});
public/javascripts/companies.js
function update_listed_companies(query) {
if(typeof query == "undefined")
query = "";
$("#company_list_container").showWith(
"/companies/livesearch?query=" + query,
false
);
}
public/javascripts/application.js
(function($) {
$.fn.showWith = function (what, popup) {
element = this;
$.get(what, function(data) {
element.html(data);
if(popup)
element.bPopup();
});
return element;
};
})(jQuery);
Here are the things that make me suspicious about the optimality of my code:
_livesearch_box.html.haml
.public/javascripts/companies_livesearch.js
I would have to hardcode the #search
part in it.#company_list_container
(which is the div in which _companies.html.haml
is rendered) hardcoded in public/javascripts/companies.js
./companies/liveseach?query=
hardcoded in public/javascript/companies.js
.app/coffeescripts/
) and compiles it in public/javascripts
. But in my application I also have some .js.erb
file in my app/views/companies
; for example, I have a voting system that uses the following in
app/views/companies/_vote.js.erb:
$("#vote_link_<%= escape_javascript(@company.id.to_s) %>").html("<%= escape_javascript(vote_link_for(@company)) %>")
To replace the "Vote this company" link with the "Unvote this company" one (and vice-versa) with an Ajax request and is rendered by the vote
and unvote
actions in the controller.
I know that there is coffee-haml-filter that compiles CoffeeScript inside haml files but it's not what I exactly need and is usually deprecated and regarded as something dirty (?).So the questions are at least:
app/views/*/*.js.*
?app/views/*/*.js.*
files at all?Sorry for the long question and thanks for getting to the end of it!
There are some solutions like js-routes (my fork) which will allow you to write Router.post_path(3)
in your JS/CS. This way you can get around hardcoding urls.
I would advise you to avoid mixing JS and Ruby. In most cases you can get around that by refactoring your JS code, the result will be easier to read and can simply be moved into a pure JS/CS-file.
# based on your vote-link example and assuming that your link
# looks like:
#
# %a(href="#"){:"data-company-id" => @company.id} Vote
# => <a href="#" data-company-id="6">Vote</a>
makeAllCompaniesVotable: () ->
$('.company a.voteLink').click ->
companyId = $(this).data('company-id')
$.ajax
url: Router.vote_company_path(companyId)
# ...
Unless you do evil eval-magic, you won't even need escape_javascript
. But you will have to remove the JavaScript from inside your partials. jquery.livequery
made the transition easier.
$(`.company`).livequery ->
# do something with $(this)
will be called each time a .company
is inserted into the document.
If you are writing code for a specific dom-tree (or a specifc view) I wouldn't consider it a bad practice. Writing unobtrusive JS is like writing CSS - and we hardcode #company_list_container
in CSS too, don't we?
$("#vote_link_<%= escape_javascript(@company.id.to_s) %>") # this is ugly though
To have an interface between the static CoffeeScript-files and the views I tend to write something like:
:javascript
$(function(){Companies.index()});
$(function(){Application.globalEnhancements()});
at the end of my views. This will then call a function I wrote with CoffeeScript, which will then enhance the site with all the needed scripts. There might be better approaches (like having a Rails-like Router for JavaScript - see Backbone.js) but it's simple and works for me.
Also if I need some data quite often (for example: the current_user):
:javascript
window.current_user = #{current_user.to_json};
However I don't think there is an efficient way to refactor. I had to do a lot of refactoring to get my ERB/JS mess removed. Still worth, though.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With