I'm trying to add an effect to my rails project using ajax. There's 4 divs on my page:
<div header></div>
<div menu></div>
<div content></div>
<div footer></div>
When a link in the menu div is clicked, the footer should slide up, and the new page text should appear in the content div. The code works fine when I have all the files in my public folder, in the rails project, but when I put files in the View folders, it doesn't work. Any idea why?
I have a scripts.js file in my assets/javascripts folder. The code is:
var ajax_loaded = (function(response) {
$("#content").slideUp(
"fast",
function() {
$("#content")
.html(response)
.slideDown("fast");
$("#content .ajax").on("click",ajax_load);
$("#content form").on("submit",form_submit);
});
});
var form_submit = (function(e) {
e.preventDefault();
var url = $(this).attr("action");
var method = $(this).attr("method");
var data = {}
$(this).find("input, textarea").each(function(i){
var name = $(this).attr("name");
var value = $(this).val();
data[name] =value;
});
$.ajax({
"url": url,
"type": method,
"data": data,
"success": function () {
history.pop();
$(history.pop()).trigger("click");
},
"error": function () {alert("bad");}
});
});
var history = [];
var current_url_method;
var ajax_load = (function(e) {
e.preventDefault();
history.push(this);
var url =$(this).attr("href");
var method = $(this).attr("data-method");
if (current_url_method != url + method) {
current_url_method = url + method;
$.ajax({
"url": url,
"type": method,
"success": ajax_loaded,
});
}
});
$("#menu a").on("click",ajax_load);
$("#menu a.main").trigger("click");
I have a style.css.scss in my assets/stylesheets folder. The code is:
#header {
background: #333333;
height:50px;
}
#menu {
background: #ffffff;
}
#content { background: #eeeeee;height:100px;}
#footer {
background: #333333;
color: #ffffff;
height: 100px;
}
In my routes folder I have:
Books::Application.routes.draw do
get "about_us", :to => "static_pages#about_us"
get "contact_us", :to => "static_pages#contact_us"
get "invite_us", :to => "static_pages#invite"
get "faq", :to => "static_pages#faq"
root :to => 'books#index'
Index method in the Books controller is:
def index
render :layout => "application"
end
In my Views/layouts, in application.html.erb, I have (this change a bit from my public folder, where links were of the form About Us):
<!DOCTYPE html>
<html>
<head>
<title>Books</title>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<div id = "header">My header</div>
<div id = "menu">
<%= link_to "About Us",about_us_path,:class => "main" %>
<%= link_to "FAQ", faq_path, :class => "main" %>
<%= link_to "Invite Others", invite_us_path, :class => "main" %>
<%= link_to "Contact Us", contact_us_path, :class => "main" %>
</div>
<div id = "content">
<%= yield %>
</div>
<div id = "footer">My footer</div>
<script src = "http://code.jquery.com/jquery-1.9.1.min.js">
</script>
</body>
</html>
Anyway, if you have any ideas on what's wrong, I'd be grateful, thanks. Like I said, it wokrs in the public folder, so I think my scripts.js is ok. Could it be something to do with the ':class => "main" ' part?
You may want to make sure scripts.js is being served when you load the page. I see that you include application.js in your layout file. This file is used as a manifest file in the Rails asset pipeline, so make sure it's configured to include scripts.js when it loads. It will need to have a line like this:
//= require scripts
In a browser, assuming you are running a rails server on port 3000, you can make sure your scripts file is accessible by visiting http://localhost:3000/assets/scripts.js. You can make sure it's included in application.js by visiting http://localhost:3000/assets/application.js.
More information on the asset pipeline: http://guides.rubyonrails.org/asset_pipeline.html
Hope this helps.
got it sorted. I wrapped all the code in my scripts.js in a
$(document).on("ready", function(){......});
That was the main thing.
Also, I was calling the jquery library twice, which probably didn't help - //= require jquery, in my application.js.erb file, and also from my application.html.erb.
Anyway, the case is resolved! Thanks for everything.
Related
I'm using simple_form with AngularJS:
= simple_form #post do |f|
= f.input :title, input_html: { "ng-model" => "title" }
It works great for my scenario on new post, but for editing on existing post, it doesn't bind/fill in existing value from post's title on form. From what I thought Rails already fill in the value, but AngularJS wipes it out after the page load because $scope.title is blank.
I found the trick is to actually create a controller with an init function that takes the value you want. In my case I just created a app/assets/javascripts/angular_app.js file that looks like this:
//= require_self
AngularRails = angular.module('AngularRails', []);
AngularRails.controller('PostFormCtrl', function($scope) {
$scope.init = function(title) {
$scope.title = title;
}
});
You'll have to translate the view into haml but it should look something like this:
<div ng-app="AngularRails">
<div ng-controller="PostFormCtrl" ng-init="init('<%= #post.title %>')">
<%= form_for #post, html: {name: "postForm", "novalidate" => true} do |f| %>
<%= f.text_field :title, "ng-model" => "title", required: true %>
<%= f.submit "ng-disabled" => "postForm.$invalid" %>
<% end %>
</div>
</div>
Remember to include the angular_app file into application.js and it should world. Obviously, this isn't a very robust solution but you could use active model serializer to convert the rails object to a json object. Then, pass that json object to the init function and in the controller, iterate over the key/value pairs of that json object and set them to $scope. Something like this:
AngularRails.controller('PostFormCtrl', function($scope) {
$scope.init = function(input) {
Object.keys(input).forEach(function(key) {
$scope[key] = input[key];
});
};
});
Hope that helps!
I would like to use the gmaps4rails gem to display a map of items in a fancybox.
I followed carefully the remarks on the wiki concerning ajax call, i.e. scripts have to be included manually in the application layout, maps have to be loaded in a javascript (see gem wiki).
But I still not succeed completely to make the map displayed in the box.
On the other hand as I hard code coordinates in the javascript it works fine, the map is displayed in the fancybox and the markers appear.
Let me recap.
In my index view, I have a ajax call to the items index action:
<%= link_to "Show Map", items_path(:format => :js, :show_map => true), :remote => true, :class => 'fancybox' %>
In the controller, I populate the map data:
def index
#items=Item.all
if params[:show_map]
#map= #items.to_gmaps4rails
end
end
in the index.js.erb file, I put
<% if params[:show_map] %>
var content = "<%= escape_javascript( gmaps({:last_map => false})) %>";
$.fancybox({
'content': content,
'padding' : 20
});
Gmaps.map = new Gmaps4RailsGoogle();
Gmaps.load_map = function() {
Gmaps.map.initialize();
Gmaps.map.markers = <%= #map %>;
Gmaps.map.create_markers();
Gmaps.map.adjustMapToBounds();
Gmaps.map.callback();
};
Gmaps.loadMaps();
<% else %>
// blablabla
<% end %>
Where the markers are provided in the map object.
This does not work and instead of my map I got in the fancybox the code itself appearing.
Something like:
var content = "\n
\n
<\/div>\n<\/div>\n"; $.fancybox({ 'content': content, 'padding' : 20 }); Gmaps.map = new Gmaps4RailsGoogle(); Gmaps.load_map = function() {Gmaps.map.initialize();
//Gmaps.map.markers = [{"lat":50.294,"lng":5.857},{"lat":50.294,"lng":5.857},{"lat":50.548,"lng":4.918},{"lat":50.384,"lng":3.649},{"lat":50.384,"lng":3.649},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.054,"lng":5.195}];
Gmaps.map.markers = [{"lat":50.8483059,"lng":4.351783999999999},{"lat":50.496,"lng":5.066},{"lat":50.11,"lng":5.003},{"lat":50.11,"lng":5.003},{"lat":50.162,"lng":5.871},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005}];
Gmaps.map.create_markers(); Gmaps.map.adjustMapToBounds(); Gmaps.map.callback(); }; Gmaps.loadMaps();
When instead of the erb <%= #map %>, I hard code the markers, for instance:
Gmaps.map.markers = [{"lat":50.294,"lng":5.857},"lat":50.294,"lng":5.857},{"lat":50.548,"lng":4.918}];
It works!
Seems like I'm missing something in the json data type conversion. But I'm not expert to find what is going wrong.
Thanks for your help!
Just successfully tried:
Open
<div id="test" style="display:none;width:300px;">
<%= gmaps markers: { data: #json } , last_map: false %>
</div>
<script type="text/javascript">
$(".fancybox").fancybox({
openEffect : 'none',
closeEffect : 'none',
afterLoad : function() { Gmaps.loadMaps(); }
});
</script>
Ok, I've got what was not going well. Thanks to the following answer https://stackoverflow.com/a/12219016/1100674.
As I use the following syntax:
Gmaps.map.markers = <%= #map %>;
I get the json rendered as this:
Gmaps.map.markers = [{"lat":50.8483059,"lng":4.351783999999999},{"lat":50.11,"lng":5.003},{"lat":50.11,"lng":5.003},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.413,"lng":4.371}];
Whereas I use the raw() method,
Gmaps.map.markers = <%= raw(#map) %>;
I get the correct format.
Gmaps.map.markers = [{"lat":50.8483059,"lng":4.351783999999999},{"lat":50.11,"lng":5.003},{"lat":50.11,"lng":5.003},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.08,"lng":4.5760000000000005},{"lat":50.413,"lng":4.371}];
Popovers do not seem to be working for me at the moment. I'm using the twitter-bootstrap gem for other functions successfully (like the carousel), and can see that tooltip and popovers are loaded in the page's source:
<script src="/assets/bootstrap-tooltip.js?body=1" type="text/javascript"></script>
<script src="/assets/bootstrap-popover.js?body=1" type="text/javascript"></script>
Here is my javascript (which I placed in application.js)
$(function () {
$('.popover-test').popover({ html : true });
});
Here is my what is in my view:
<%= link_to('Popover test', '#', :class => "popover-test", :title => "This is a test", "data-content" => "<b>hello popover world</b>") %>
Any help would be greatly appreciated, thank you!
You're not initialising the popover. Try:
$(function () {
$('.popover-test').popover({
html : true
}).popover('show');
});
Note the .popover('show')
I'd also recommend taking the content logic out of your html so your final result looks like:
$(function () {
$('.popover-test').popover({
html : true,
title: "This is a test",
content: "<b>hello popover world</b>"
}).popover('show');
});
...much cleaner
Try this markup:
<%= link_to('Popover test', '#', :class => "popover-test", :rel => "popover", :title => "This is a test", :"data-content" => "<b>hello popover world</b>") %>
You were just missing the :rel value, and "data-content" should be a symbol.
Did you get this working? If not, what wisew said is correct, however, he has a small typo -> remove the quotes around "data-content":
<%= link_to('Popover test', '#', :class => "popover-test", :rel => "popover", :title => "This is a test", :data-content => "<b>hello popover world</b>") %>
See if that does it
I'm using Rails 3.0.9 version, and jquery.
I've been using this gem without a database. It is used only for map display, and display KML file on it. For this I used:
<div id='ajax_map'>
<% #kmlurl="http://mysite/file1.kml" %>
<%= gmaps( :kml => { :data => "[{ url: #{#kmlurl.inspect}}]" } ) %>
</div>
All great shows.
I want to do that after you change the links (# kmlurl), and click on the button, the map updated with this new KML file. I use a separate action js.erb with the following code:
$('#ajax_map').html('<%= #kmlurl="http://mysite/file2.kml" %>'+'<br />'+'<%= gmaps( :kml => { :data => "[{ url: #{#kmlurl.inspect}}]" } ) %>');
But he does not update the DIV. "js.erb" rendered normally, without using the method of gmaps () it normally returns # kmlurl. I tested this same code in the ". Html.erb" in the tags , it loads a new file, but, of course, just when the page loads.
How can I solve this problem?
Solved the problem as follows (in js.erb):
$('#ajax_map').html('<%= escape_javascript( gmaps({:last_map => false}) ) %>');
Gmaps.map = new Gmaps4RailsGoogle();
Gmaps.load_map = function() {
Gmaps.map.map_options.maxZoom = 15;
Gmaps.map.initialize();
Gmaps.map.kml = [{ url: '<%= "#{#kmlurl}" %>'}];
Gmaps.map.create_kml();
Gmaps.map.adjustMapToBounds();
Gmaps.map.callback();
};
Gmaps.loadMaps();
First I would refactor things just a bit.
Say that first bit of code were in your index page. I'd move the setting of #kmlurl into the corresponding controller action:
def index
#kmlurl = "http://mysite/file1.kml"
end
Then (assuming index?) your index view would be simply:
<div id="ajax_map">
<%= gmaps( :kml => { :data => "[{ url: #{#kmlurl}}]" } ) %>
</div>
Then to add a link that will update the map:
<%= link_to 'Other Map', '/othermap', :remote=>true %>
Now you'd create a route in routes.rb:
match '/othermap' => 'foo#othermap'
Then in foo_controller.rb:
def othermap
#kmlurl = "http://mysite/file2.kml"
end
Then create othermap.js.erb:
$('#ajax_map').html(
'<%=
escape_javascript(
gmaps( :kml => { :data => "[{ url: #{#kmlurl}}]" } )
)
%>'
)
That's a quick fix, but what I would REALLY do is strive to make your view code as simple as possible, and do all the real work in the controller. Ideally your view would just be:
<div id="ajax_map">
<%= gmaps( :kml => { :data => #mapdata } ) %>
</div>
set up #mapdata as appropriate in your controller. You've got too much stuff that really belongs in a controller embedded in your view code! Your othermap.js.erb should be equally simplified. i.e.
$('#ajax_map').html('<%= escape_javascript(gmaps( :kml => { :data => #mapdata } ))%>')
I'm looking to add functionality to my Rails app to upload files directly to Amazon S3. From my research the general consensus seems to be to use the s3-swf-upload-plugin. I've setup a sample app using that gem but I can't get it to play nice with only allowing the selection of a single file. I'd also like to create a record post upload and use paperclip to create a thumbnail for which I can find little guidance.
So my questions are:
(1) am I on the right track using that gem or should I be taking another appraoch?
(2) are there any samples out there that I could use for reference?
Any assistance would be much appreciated.
Chris
Try a new Gem called CarrierWaveDirect it allows you to upload files direct to S3 using a html form and easily move the image processing into a background process
Not sure about whether you can modify it easily to only upload one file at a time, but this gem works very well for me. It is based on one of Ryan Bates' Railscast:
https://github.com/waynehoover/s3_direct_upload
Try looking into carrierwave https://github.com/jnicklas/carrierwave (supports s3)
Multi file uploads with carrierwave and uploadify http://blog.assimov.net/post/4306595758/multi-file-upload-with-uploadify-and-carrierwave-on
If you are using Rails 3, please check out my sample projects:
Sample project using Rails 3, Flash and MooTools-based FancyUploader to upload directly to S3: https://github.com/iwasrobbed/Rails3-S3-Uploader-FancyUploader
Sample project using Rails 3, Flash/Silverlight/GoogleGears/BrowserPlus and jQuery-based Plupload to upload directly to S3: https://github.com/iwasrobbed/Rails3-S3-Uploader-Plupload
By the way, you can do post-processing with Paperclip using something like this blog post describes:
http://www.railstoolkit.com/posts/fancyupload-amazon-s3-uploader-with-paperclip
I have adapted Heroku's direct to S3 upload solution in Rails (which uses jQuery-File-Upload and the aws-sdk gem) so uploads to S3 can be made remotely using ajax. I hope this is useful:
posts_controller.rb
before_action :set_s3_direct_post, only: [:index, :create]
before_action :delete_picture_from_s3, only: [:destroy]
class PostsController < ApplicationController
def index
.
.
end
def create
#post = #user.posts.build(post_params)
if #post.save
format.html
format.js
end
end
def destroy
Post.find(params[:id]).destroy
end
private
def set_s3_direct_post
return S3_BUCKET.presigned_post(key: "uploads/#{SecureRandom.uuid}/${filename}", success_action_status: '201', acl: 'public-read')
end
def delete_picture_from_s3
key = params[:picture_url].split('amazonaws.com/')[1]
S3_BUCKET.object(key).delete
return true
rescue => e
# If anyone knows a good way to deal with a defunct file sitting in the bucket, please speak up.
return true
end
def post_params
params.require(:post).permit(:content, :picture_url)
end
end
posts.html.erb
<div class="info" data-url="<%= #s3_direct_post.url %>"
data-formdata="<%= (#s3_direct_post.fields.to_json) %>"
data-host="<%= URI.parse(#s3_direct_post.url).host %>">
</div>
The form
<%= form_for(:post, url: :posts, method: :post,
html: { class: "post_form", id: "post_form-#{post.id}" }
) do |f| %>
<%= f.text_area :content, id: "postfield-#{post.id}", class: "postText" %>
<%= f.button( :submit, name: "Post", title: "Post" ) do %>
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
<% end %>
<span class="postuploadbutton" id="postUp-<%= post.id %>" title="Add file" >
<span class="glyphicon glyphicon-upload" aria-hidden="true"></span>
</span>
<span title="Cancel file" class="noticecancelupload" id="postCancel-<%= post.id %>" >
<span class="glyphicon glyphicon-remove-circle" aria-hidden="true"></span>
</span>
<%= f.file_field :picture_url, accept: 'image/jpeg,image/gif,image/png',
class: "notice_file_field", id: "postFile-#{post.id}" %>
<% end %>
_post.html.erb
<%= button_to post_path(
params: {
id: post.id,
picture_url: post.picture_url
}
),
class: 'btn btn-default btn-xs blurme',
data: { confirm: "Delete post: are you sure?" },
method: :delete do %>
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
<% end %>
Javascript in each _post.html.erb
$(document).off('click',"#postUp-<%= post.id %>");
$(document).on('click', '#postUp-<%= post.id %>', function(e) {
prepareUpload("#post_form-<%= post.id %>");
$('#postFile-<%= post.id %>').trigger("click");
});
$(document).off('click',"#postCancel-<%= post.id %>");
$(document).on('click', '#postCancel-<%= post.id %>', function(e) {
$(".appendedInput").remove(); // $('#postFile-<% post.id %>').val(""); doesn't work for me
$('.progBar').css('background','white').text("");
});
$(document).off('submit',"#post_form-<%= post.id %>"); // without this the form submitted multiple times in production
$(document).on('submit', '#post_form-<%= post.id %>', function(e) { // don't use $('#post_form-<%= post.id %>').submit(function() { so it doesn't bind to the #post_form (so it still works after ajax loading)
e.preventDefault(); // prevent normal form submission
if ( validatePostForm('<%= post.id %>') ) {
$.ajax({
type: 'POST',
url: $(this).attr('action'),
data: $(this).serialize(),
dataType: 'script'
});
$('#postCancel-<%= post.id %>').trigger("click");
}
});
function validatePostForm(postid) {
if ( jQuery.isBlank($('#postfield-' + postid).val()) && jQuery.isBlank($('#postFile-' + postid).val()) ) {
alert("Write something fascinating or add a picture.");
return false;
} else {
return true;
}
}
Javascript in application.js
function prepareUpload(feckid) {
$(feckid).find("input:file").each(function(i, elem) {
var fileInput = $(elem);
var progressBar = $("<div class='progBar'></div>");
var barContainer = $("<div class='progress'></div>").append(progressBar);
fileInput.after(barContainer);
var maxFS = 10 * 1024 * 1024;
var info = $(".info");
var urlnumbnuts = info.attr("data-url");
var formdatanumbnuts = jQuery.parseJSON(info.attr("data-formdata"));
var hostnumbnuts = info.attr("data-host");
var form = $(fileInput.parents('form:first'));
fileInput.fileupload({
fileInput: fileInput,
maxFileSize: maxFS,
url: urlnumbnuts,
type: 'POST',
autoUpload: true,
formData: formdatanumbnuts,
paramName: 'file',
dataType: 'XML',
replaceFileInput: false,
add: function (e, data) {
$.each(data.files, function (index, file) {
if (file.size > maxFS) {
alert('Alas, the file exceeds the maximum file size of 10MB.');
form[0].reset();
return false;
} else {
data.submit();
return true;
}
});
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
progressBar.css('width', progress + '%')
},
start: function (e) {
progressBar.
css('background', 'orange').
css('display', 'block').
css('width', '0%').
text("Preparing...");
},
done: function(e, data) {
var key = $(data.jqXHR.responseXML).find("Key").text();
var url = '//' + hostnumbnuts + '/' + key;
var input = $('<input />', { type:'hidden', class:'appendedInput',
name: fileInput.attr('name'), value: url });
form.append(input);
progressBar.
css('background', 'green').
text("Ready");
},
fail: function(e, data) {
progressBar.
css("background", "red").
css("color", "black").
text("Failed");
}
});
});
} // function prepareUpload()
create.js.erb
$(".info").attr("data-formdata", '<%=raw #s3_direct_post.fields.to_json %>'); // don't use .data() to set attributes
$(".info").attr("data-url", "<%= #s3_direct_post.url %>");
$(".info").attr("data-host", "<%= URI.parse(#s3_direct_post.url).host %>");
$('.post_form')[0].reset();
$('.postText').val('');
application.js
//= require jquery-fileupload/basic
config/initializers/aws.rb
Aws.config.update({
region: 'us-east-1',
credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']),
})
S3_BUCKET = Aws::S3::Resource.new.bucket(ENV['S3_BUCKET'])
Notes:
This solution is designed for multiple post forms on the index.html.erb page. This is why the #s3_direct_post information is placed inside a div of class info inside index.html.erb, rather than in each post form. This means there is only one #s3_direct_post presented on the page at any one time, irrespective of the number of forms on the page. The data inside the #s3_direct_post is only grabbed (with a call to prepareUpload()) upon clicking the file upload button. Upon submission a fresh #s3_direct_post is generated in the posts controller, and the information inside .info is updated by create.js.erb. Storing the #s3_direct_post data inside the form means many different instances of #s3_direct_post can exist at once, leading to errors with the file name generation.
You need to :set_s3_direct_post in both the posts controller index action (ready for the first upload) and the create action (ready for the second and subsequent uploads).
Normal form submission is prevented by e.preventDefault(); so it can be done 'manually' with $.ajax({. Why not just use remote: true in the form? Because in Rails, file upload is done with an HTML request and page refresh even when you try to do it remotely.
Use info.attr() rather than info.data() to set and retrieve the #s3_direct_post attributes because info.data doesn't get updated
(for example see this question). This means you also have to manually parse the attribute into an object using jQuery.parseJSON() (which .data() actually does automatically).
Don't use //= require jquery-fileupload in application.js. This bug was a real ballache to identify (see here). The original Heroku solution didn't work until I changed this.
You can use Paperclip to upload to S3 (see documentation) and to create thumbnails, although it uploads to temporary folder first, after that image processing can be applied before uploading file to S3.
As for the examples of such configuration, there are plenty of them throughout the blogosphere and on StackOverflow, e.g. this.