I am a newbie with rails, I want to use the Twitter-Bootstrap hover popover in my rails app.
I have the following part:
<div class="field">
<%= f.label :first_name, {:class => 'label'}%>
<%= f.text_field :first_name ,{:class => 'span3',}%>
</div>
If the user hover on the :first_name label, a message appear telling him something. But I don't know where to put things in my app like the js files reference or the function.
I tried this example and put the javascript_include_tag in the layout/application.html.erb
But seems that I don't know how to get it works. So I need a detailed description about how to use it.
There are several* Rails* Bootstrap* projects that you can use for integrating Bootstrap.
If you would rather do it manually, one option is to place the files (including any plugins you want to use, which in your case would be bootstrap-tooltip.js and bootstrap-popover.js) into /vendor/assets/javascripts and include them in your application.js like so:
//= require bootstrap-min.js
//= require bootstrap-tooltip.js
//= require bootstrap-popover.js
Do the same with the bootstrap.css CSS file by dropping it into /vendor/assets/stylesheets and including it in /app/assets/stylesheets/application.css by adding this line below *= require_self:
*= require bootstrap
I personally prefer using the full bootstrap.css file instead of the minified bootstrap-min because I occasionally want to browse the source. Either way, when you deploy to production, the CSS will be minified by Rails automatically via the asset pipeline.
Once you have Bootstrap loaded in, you can use the following snippet to initialize the popover plugin on the element of your choice:
$('.label-with-popover').popover(placement: 'right') # Note: 'right' is default
You can place the above snippet at the bottom of your application.js but the recommended way is to place it in the .coffee file that is respective to the scaffold you are working with, for example users.js.coffee
And finally..
<label class="label-with-popover"
data-content="popover content"
data-title="popover title">
...
I added a popover helper using something like:
module PopoverHelper
def popover(model_name, attribute)
i18n_base = "simple_form.popovers.#{model_name.downcase}.#{attribute}"
content_tag(:i, '', class: "icon-question-sign",
id: "#{attribute}_help",
title: I18n.t("#{i18n_base}.title"),
data: {
# don't use popover as it conflicts with the actual pop-over thingy
pop_over: true,
content: I18n.t("#{i18n_base}.text")
})
end
end
In your view you can run the js:
$("[data-pop-over]").popover({placement: 'right'});
Along with your helper:
= popover(:model, :attribute)
This is provided that you have bootstrap already loaded as well as jquery. You can sub out the i18n with hard-coded text if you wish.
Related
I've successfully added ujs in one rails (3.2.6) app. Adding the :remote => true to my form tag allows me to make ajax calls to my js.erb files for dynamic loading of divs, ect..
But in another application on the same machine (Ubuntu 12.0.4), is seems the ujs engine is not working. I'm always getting a Template not Found because the form is sending format => html rather than js. If I force the form to use js format (format => 'js'), it then just renders the js.erb file, rather than calling it via ajax.
In the application.js, I've included the proper headers with the following:
//= require jquery
//= require jquery_ujs
//= require_tree .
The javascript files are included when I actually browse to the primary home page which is using the application layout, which includes the above mentioned javascirpt references. The form in the page is as follows:
<%= form_tag list_path, :remote => true, :id => 'frmBookResults', :method => :post do %>
But although it contains :remote => true, and there's a route established for list_path (the route works, because if I change the list.js.erb to list.html.erb, the view renders), and a method in the controller to handle the request (def list....end), the subsequent list.js.erb is ignored and I get a template not found error, because rails is processing the form request as html, which I can confirm in the log.
I've searched everywhere I could for a solution, but can't figure out why my ujs isn't working for this particular app, when it is nearly identical to my working app, gemset, versions, and configuration.
I've found a couple of other articles on stack overflow where people had the same problem, but no final, working answer was given.
Any help or direction would be greatly appreciated.
It was indeed an issue with the ajax being broken. The onkeyup trigger I was using to submit the form was as follows:
<%= form_tag list_path, :id => 'frmBookResults', :remote => true do %>
<input id='keyword' type='text' onkeyup='document.forms["frmBookResults"].submit();'/>
<% end %>
Note the following:
document.forms["frmBookResults"].submit();
Apparently, submitting the form via javascript was the issue, because when I updated the onkeyup to use a jquery submit as described below, rails ujs kicked in and the ajax calls to my list.js.erb worked:
onkeyup="jQuery('#frmBookResults').submit()"
Thanks for all the feedback mccanff! Your contribution along with other developers from the rails group at linkedIn helped me finally solve my issue.
I have standard rails app format.
I have these controller:
class StaticPagesController < ApplicationController
def help
end
def about
end
end
and I have the file app/assets/javascripts/static_page.js.coffee
I want 2 things:
load this javascript ONLY when I am running one of the pages of the static_pages
be able to make different js calls depending on the specific action inside the controller:
the javascript file should look like:
//general javascript code
if (isThisHelpPage) {
//Run some help page code
}
if (isThisAboutPage) {
//Run some about page code
}
I assume that the first issue should be solved somehow with the app/assets/javascripts/application.js file by adding some rule like:
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require_tree .
//= (isItStaticController? render static_page.js.coffee)
But I want to know how can I do that?
Unfortunately you can't add conditionals to the application js file as it is precompiled when the app is deployed.
There are two potential approaches that I can see working for you here:
1) Place all of your javascript in a single file as you have above then set the page in your code like this:
<script> page = "Help" </script> or <script> page = "About" </script>
your script could then become:
if(page === "Help") .... else if(page === "About") ....
2) the other option would be to create separate js files for each page, and then incorporate them via a yield :head block in your layout. In your help file it would look like this:
<% content_for :head %>
<%= javascript_include_tag 'help' %>
<% end %>
I personally favour the second approach in my apps.
I'll explain how I handle this. In my ApplicationController I have a method that runs from a before_filter on every request.
def prepare_common_variables
controller_name = self.class.name.gsub(/Controller$/, '')
if !controller_name.index('::').nil?
namespace, controller_name = controller_name.split('::')
end
#default_body_classes = ["#{controller_name.underscore}_#{action_name} ".downcase.strip]
#default_body_classes = ["#{namespace.underscore}_#{#default_body_classes.join}".strip] if !namespace.nil?
end
In app/views/layouts/application.html.erb I have the following
<body class="<%= yield :body_classes %> <%= #default_body_classes.join(' ') %>">
For your StaticPagesController, when the help action runs, this would generate the following <body> tag:
<body class="static_pages_help">
Next, I have a method like this in my app/assets/javascripts/application.js.erb
Array.prototype.diff = function(a) {
return this.filter(function(i) {return !(a.indexOf(i) > -1);});
};
var DEEFOUR = (function (deefour) {
deefour.Utility = (function (utility) {
utility.hasBodyClass = function() {
var args = Array.prototype.slice.call(arguments);
if (args.length === 0 || $('body').get(0).attr('class') == "") return false;
return args.diff($('body').get(0).attr('class').split(/\s/)).length == 0;
};
return utility;
}(deefour.Utility || {}));
return deefour;
}(DEEFOUR || {}));
Finally, in my equivalent of your app/assets/javascripts/static_page.js.coffee I will have something like this
$(function(){
if (!DEEFOUR.Utility.hasBodyClass('static_pages_help')) return;
// code for your help page
});
$(function(){
if (!DEEFOUR.Utility.hasBodyClass('static_pages_about')) return;
// code for your about page
});
This is nice because in your view
<% content_for :body_classes, :some_custom_class %>
or within a specific action
#default_body_classes << "some_other_custom_class"
you can conditionally add specific classes to match against in your Javascript.
// *both* 'static_pages_help' and 'some_other_class' are required
if (!DEEFOUR.Utility.hasBodyClass('static_pages_help') || !DEEFOUR.Utility.hasBodyClass('some_other_class')) return;
hasBodyClass(...) accepts an arbitrary # of arguments; just list them out. This is useful for things like a new and create action which you want the same Javascript to run for when a form fails to submit.
if (!DEEFOUR.Utility.hasBodyClass('some_controller_new', 'some_controller_create')) return;
It should be noted, prepare_common_variables needs a bit of tweaking as it only allows for a single namespace like SomeNamespace::TheController and not more like SomeNamespace::AnotherNamespace::TheController.
The best practice here is to use the content_for helper method. Kind of like described by #Adam, but you probably want to put a <%= yield :script_files %> in the bottom of your layout file, and then call
<% content_for :script_files do %>
<%= javascript_include_tag 'your_js_file' %>
<% end %>
from within the action you want it to be. You can do this even cleaner by following a similar approach as mentioned by Ryan Bates in an early Railscast:
module ApplicationHelper
def javascripts(paths)
content_for :script_files do
javascript_include_tag(paths.is_a?(Array) ? paths.join(',') : paths)
end
end
end
You can then, from your action view, just call <% javascripts 'your_js_file' %> and have that file included.
load this javascript ONLY when I am running one of the pages of the static_pages
First of all, asset pipeline compiles and creates static asset files offline, meaning that you can not create bundles dynamically based on the controller name during the serving of a user request (in production).
However, you can use a separate <%= javascript_include_tag "static_page_manifest.js" %> statement instead of (including the application.js) in the corresponding layout file (of StaticPages controller) to define certain javascripts for the specific views. For example, you can have a app/assets/javascripts/static_page_manifest.js:
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require_tree .
//= static_page
Alternatively, if you want dynamic javascript module inclusion, you could use requirejs (requirejs-rails) etc.
be able to make different js calls depending on the specific action inside the controller
This could be achieved by checking an body tag id with a value depending on controller and action names! For example:
<body id="static_pages_<%= controller.action_name %>">
...
And then you can check this id value in the javascript bundle to execute certain code snippets.
I have an erb template in which I need to use:
CGI.unescapeHTML(someEscapedHTML)
So I need to require 'cgi', however the following fails:
<% require 'cgi' %>
With the error:
can't dup NilClass
I would personally never put a require statement in a view, because 1) it's ugly and 2) what if another view needed that require?
A better place for this is in config/application.rb at the bottom, or in a file in config/initializers.
First of all do not require gems or libraries in ERB please. Then CGI is required by Rails itself already.
If you want to prevent Rails 3 from auto-escaping consider using
<%= data.html_safe %>
instead.
Edit: a year later if I was going to do this again I'd do it with curl.js instead of Rails asset pipeline.
Related: Best way to add page specific javascript in a Rails 3 app?
I'm writing an app and using coffeescript to generate all of the js. That's why the related question doesn't do what I need.
I'd like to be able to put a coffeescript file in a subfolder of my assets directory and have that .coffee file only get served up on one page. The page is on a named route
match 'myNotifications' => 'user#notifications'
The most obvious thing to do was to put the .coffee file in assets\javascripts\user\index.js.coffee. But after reading the docs about assets I'm unclear.
I read this line (from http://guides.rubyonrails.org/asset_pipeline.html):
You should put any JavaScript or CSS unique to a controller inside
their respective asset files, as these files can then be loaded just
for these controllers with lines such as <%= javascript_include_tag
params[:controller] %> or <%= stylesheet_link_tag params[:controller]
%>.
Ok cool, so I put the page specific js in assets\javascripts\user.js.coffee. Then I reloaded my home page, Ctrl F5. The user.js file is still being loaded on the homepage. Tested with $ -> alert 'ready from users controller'; seeing that alert when I load the homepage.
Does Rails have a way to have a per-page coffeescript file that will only be served up with that page? Am I reading the manual wrong? Is there a place in the assets folder that I can put .coffee files where they won't get loaded with every page?
Update: Looks like I might have an answer:
There are a couple of ways that we can get around this problem. We
could use require_directory instead of require_tree as this will only
load the files in the current directory and not in subdirectories. If
we want more control over the included files we can require them
separately instead of including the whole directory. Alternatively we
could move the JavaScript files that we want to be included on all
pages into a public subdirectory. We can then use require_tree
./public to include just those files.
I'll give that a shot in the AM.
Here's the approach I use to make controller/view specific Coffee:
application.html.haml:
%body{ :data => { :controller => params[:controller], :action => params[:action]} }
alt. application.html.erb
<%= content_tag(:body, :data => { :controller => params[:controller], :action => params[:action] }) do %>
...
<% end %>
application.js.coffee:
$(document).ready ->
load_javascript($("body").data('controller'),$("body").data('action'))
load_javascript = (controller,action) ->
$.event.trigger "#{controller}.load"
$.event.trigger "#{action}_#{controller}.load"
users.js.coffee
$(document).bind 'edit_users.load', (e,obj) =>
# fire on edit users controller action
$(document).bind 'show_users.load', (e,obj) =>
# fire on show users controller action
$(document).bind 'users.load', (e,obj) =>
# fire on all users controller actions
Sidenote:
This works great with PJAX as well as you can pass the controller/action names with the response header on PJAX requests and just fire these js functions based on that.
EDIT (2014/03/04):
This solution still works when using turbolinks.js.
Rather than only including the file on one page, you might want to just use logic that's conditional on the page markup. See my answer to a related question. That way, your users don't have to make an additional <script> request for the particular page.
If there's a lot of logic specific to that page (say, 10K+ minified), then yes, split it out. As you suggested in the edit to your question: Rather than doing require_tree . at the root of your javascripts directory, instead create a sub-directory called global and change the top of application.js from
require_tree .
to
require_tree global
Then put your page-specific CoffeeScript file in the root javascripts directory, and point to it with a javascript_include_tag call in that page's template.
include javascript tag into your view template, like show.html.haml
- content_for :javascripts do
= javascript_include_tag 'folder/coffee_file_name'
If I generate a new controller in Rails 3.1, also a javascript file with the name of the controller will added automatically. Firstly, I thought this javascript file will used only, when the related controller is called.
By default there is the instruction //= require_tree . in the application.js-file, that include every javascript file on it's tree.
How could I load only the controller specific script?
To load only the necessary name_of_the_js_file.js file:
remove the //=require_tree from application.js
keep your js file (that you want to load when a specific page is loaded) in the asset pipeline
add a helper in application_helper.rb
def javascript(*files)
content_for(:head) { javascript_include_tag(*files) }
end
yield into your layout:
<%= yield(:head) %>
add this in your view file:
<% javascript 'name_of_the_js_file' %>
Then it should be ok
An elegant solution for this is to require controller_name in your javascript_include_tag
see http://apidock.com/rails/ActionController/Metal/controller_name/class
<%= javascript_include_tag "application", controller_name %>
controller_name.js will be loaded and is in the asset also, so you can require other files from here.
Example, rendering cars#index will give
<%= javascript_include_tag "application", "cars" %>
where cars.js can contain
//= require wheel
//= require tyre
Enjoy !
I always include this inside my layout files. It can scope your js to action
<%= javascript_include_tag params[:controller] if AppName::Application.assets.find_asset("#{params[:controller]}.js") %>
<%= javascript_include_tag "#{params[:controller]}_#{params[:action]}" if AppName::Application.assets.find_asset("#{params[:controller]}_#{params[:action]}.js") %>
Your problem can be solved in different ways.
Add the assets dynamically
Please consider that this isn't a good solution for the production mode, because your controller specifics won't be precompiled!
Add to our application helper the following method:
module ApplicationHelper
def include_related_asset(asset)
# v-----{Change this}
if !YourApp::Application.assets.find_asset(asset).nil?
case asset.split('.')[-1]
when 'js'
javascript_include_tag asset
when 'css'
stylesheet_link_tag asset
end
end
end
end
Call the helper method in your layout-file:
<%= include_related_asset(params[:controller].to_param + '_' + params[:action].to_param . 'js') %>
Create specific assets for your controller actions. E. g. controller_action.js
Please don't forget to change YourApp to the name of your app.
Use yield
Add <%= yield :head%> to your layout head
Include your assets from your action views:
<% content_for :head do %>
<%= javascript_include_tag 'controller_action' %>
<% end %>
Please see the Rails guides for further information.
I like albandiguer's solution. With which I've found that javascript/coffeescript assets are not individually precompiled. Which causes all sorts of errors trying to use javascript_path. I'll share my solution to that problem after I address an issue a few people mentioned in his comments. Mainly dealing with only a partial set of controller named JavaScript files.
So I built an application helper to detect if the file exists in the javascript directory regardless of .coffee/.js extension:
module ApplicationHelper
def javascript_asset_path(basename)
Sprockets::Rails::Helper.assets.paths.select{|i|
i =~ /javascript/ and i =~ /#{Rails.root}/
}.each do |directory|
if Dir.entries(directory).map {|i| i.split('.')[0]}.compact.
include? basename
return File.join(directory, basename)
end
end
nil
end
end
This method will return the full path to the javascript file if it exists. Otherwise it returns nil. So following Pencilcheck's comment you can add this method for a conditional include:
<%= javascript_include_tag(controller_name) if javascript_asset_path(controller_name) %>
And now you have a proper conditional include. Now for the issue of precompiled assets. Generally for optimization you don't want assets precompiled individually. You can however do it if you must:
# Live Compilation
config.assets.compile = true
You can add this do your environment config file. Test it in your development environment file first. Again this is ill-advisable. The Rails asset pipeline uses Sprockets to optimize everything:
Sprockets loads the files specified, processes them if necessary,
concatenates them into one single file and then compresses them (if
Rails.application.config.assets.compress is true). By serving one file
rather than many, the load time of pages can be greatly reduced
because the browser makes fewer requests. Compression also reduces
file size, enabling the browser to download them faster.
PLEASE READ the documentation for further details of the mechanics of Sprockets (Asset Pipeline) http://guides.rubyonrails.org/asset_pipeline.html
Assets aren't precompiled individually. For example when I try:
<%= javascript_include_tag 'event' %>
I get:
Sprockets::Rails::Helper::AssetFilteredError: Asset filtered out and
will not be served: add Rails.application.config.assets.precompile +=
%w( event.js ) to config/initializers/assets.rb and restart your
server
So you can include which assets to be precompiled individually. We just need to add the relevant controller named javascript files in our asset initializer. Well we can do this programatically.
To get a list of controller names I will use ecoologic's example:
all_controllers = Dir[
Rails.root.join('app/controllers/*_controller.rb')
].map { |path|
path.match(/(\w+)_controller.rb/); $1
}.compact
And now to get the name of all javascript files that match the basename of the controller name you can use the following:
javascripts_of_controllers = Sprockets::Rails::Helper.assets.paths.select{|a_path|
a_path =~ /javascript/ and a_path =~ /#{Rails.root}/
}.map {|a_path|
Dir.entries(a_path)
}.flatten.delete_if {|the_file|
!the_file['.js']
}.collect {|the_file|
the_file if all_controllers.any? {|a_controller| the_file[a_controller]}
}
Then you can try:
# config/initializers/assets.rb
Rails.application.config.assets.precompile += javascripts_of_controllers
This will get you a list of all javascript files, without directory path, that match your controller name. Note if your controller name is plural, the javascript name should be as well. Also note if the controller is singular and the javascript file is plural this will still include it because of the_file[a_controller] will succeed on a partial match.
Feel free to try this out in your Rails.application.config.assets.precompile setting. I know that this gets you the list of files correctly. But I'll leave you to test it. Let me know if there are any nuances involved with precompiling this way as I am curious.
For a very thorough explanation on how assets precompile see this blog: http://www.sitepoint.com/asset-precompile-works-part/
I recently found a simple approach to use generated scripts for specific controller. I use for that solution gem gon. Add in a controller:
class HomesController < ApplicationController
before_filter :remember_controller
private
def remember_controller
gon.controller = params[:controller]
end
end
After that open your homes.js.cofee and add in the beginning of file:
jQuery ->
if gon.controller == "sermons"
# Place all functions here...
That is all.