I am trying to get ajax image uploading working in my rails app. I am using Paperclip for the normal image uploading, and that works fine, but I can't seem to get the ajax method hooked up. I am using the Rack RawUpload and the
File Uploader plugin. I followed the general instructions here, but I am stuck at the actually attaching the image to the new object on create. Here is my Controller code:
#bottle = Bottle.new(params[:bottle])
#bottle.user_id = current_user.id
#file = params[:qqfile].is_a?(ActionDispatch::Http::UploadedFile) ? params[:qqfile] : params[:file]
is_qq = params.has_key?(:qqfile)
if is_qq
params[:bottle][:image] = params.delete(:file)
render :json => { "success" => true }
else
respond_to do |format|
if #bottle.save
format.html { redirect_to '/bottles', notice: 'Bottle was successfully created.' }
format.json { render json: #bottle, status: :created, location: #bottle }
else
format.html { render action: "new" }
format.json { render json: #bottle.errors, status: :unprocessable_entity }
end
end
end
and Here is the view code:
<%= simple_form_for(#bottle, :html => { :multipart => true }) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :brand, :placeholder => 'Brand', :label => false %>
<%= f.input :region, :placeholder => 'Region', :label => false %>
<%= f.input :age, :collection => 5..35, :prompt => "Bottle Age", :label => false %>
<%= f.input :price, :placeholder => 'Price', :label => false, :as => :currency, :input_html => { :class => 'span2' } %>
<%= f.input :image_id, :as => :hidden %>
<%= f.text_area :notes, :placeholder => 'Tasting notes...', :size => "160x5" %>
</div>
</div>
<div class="span3 offset2">
Drag a file from your desktop here...
<div class="well" height="105" style="width:200px;height:300px;">
<!-- <img src="http://placehold.it/200x300" alt="" class="temp_image"> -->
<div id="file-uploader"></div>
</div>
or...
<%= f.file_field :image %>
</div>
<div class="span8 offset2">
<%= f.button :submit, :class => 'btn-primary' %>
<%= link_to bottles_path, :class => 'btn btn-danger' do %>
Cancel
<% end %>
</div>
<% end %>
I am uploading with the File Uploader like this:
var uploader = new qq.FileUploader({
debug: false,
/* Do not use the jQuery selector here */
element: document.getElementById("file-uploader"),
action: '/bottles',
allowedExtensions: ["jpg", "png"],
/*
* This uploads via browser memory. 1 MB example.
*/
sizeLimit: 1048576,
/* Set Article category on submit */
onSubmit: function(id, fileName) {
uploader.setParams({
authenticity_token: $("input[name='authenticity_token']").attr("value")
});
},
onComplete: function(id, fileName, responseJSON){
url = responseJSON.image.image.url;
$('.well').html('<img src="'+url+'" />');
$('input#bottle_image_id').val(responseJSON.image.id);
}
});
It seems to upload using Rack fine and it passes the :file param to the method, but I can't assign param[:bottle][:image] with the param[:file], i get error:
undefined method `[]=' for nil:NilClass
Any help would be greatly appreciated.
EDIT:
So I am able to get the ajax upload to hook into the paperclip upload and add the appropriate parameters, but now I need to update that same object when I submit the rest of the form, not create a new object. How can I store the object created by the ajax upload that contains all the image content and update it once the full form is submitted?
EDIT 2:
The error i get when saving is
undefined method `save' for #<ActiveSupport::HashWithIndifferentAccess:0x007ff961d08e48>
Which I assume is because I am taking the form data and trying to put it in the same #bottle object and the hashes are not matching up.
I patch lib/rack/request.rb at 217 line to solve this problem
replace
{}
with
#env["rack.request.form_hash"] || {}
Related
I'm having issues with a form in my Rails 6 application.
I have a remote form which enters new data into the database via a JSON request, which works but only with one parameter. This is the form:
<%= form_with(method: :post, remote: true, model: Meal.new, url: meals_path, class: "editMealsForm", data: { type: "json" }) do |f| %>
<div class="field">
<%= f.label :description, 'Beschreibung' %><br>
<%= f.text_area :description, rows: "4", placeholder: "Noch keine Mahlzeit für diesen Tag vorhanden, bitte eintragen!", class: "form-control" %>
</div>
<div class="field">
<%= f.label :allergene_ids_add, 'Allergene auswählen', class: "card-text" %><br>
<%= select_tag :allergene_ids_add, options_from_collection_for_select(#allergenes, :id, :name), multiple: true, class: "selectpicker", data: { style: "btn-success", width: "fit", live_search: "true", size: "5", title: "Allergien wählen" } %>
</div>
<% f.hidden_field :day, :value => local_assigns[:mealDay] %>
<% f.hidden_field :tip, :value => "Vollkost" %>
<%= f.submit "Speichern", class: "btn btn-primary mt-2 btn-block" %>
<% end %>
And these are my permitted parameters:
def meal_params
params.require(:meal).permit(:day, :tip, :description, allergene_ids_add: [])
end
And this is my controller action:
def create
#meal = Meal.new(meal_params)
byebug
if #meal.save
if params[:allergene_ids_add].present?
#allergenes_to_add = Allergene.find(params[:allergene_ids_add])
#allergenes_to_add.each do |allergene|
#meal.allergenes << allergene
end
end
respond_to do |format|
format.html { redirect_to meals_path }
format.json { render json: #meal }
end
end
end
The problem is, that if I hit the create action, just the description parameter is permitted, the other ones are just "ignored", so if I fire the submit button I get the following output in the console if I hit my byebug breakpoint:
And if I look at the params:
<ActionController::Parameters {"authenticity_token"=>"derBZeirq0bwr/FWoYRr97qUZ5p66vQc+uT+UMf5xjXXTSFEp+XOepJtGrckguGh+skWXTZ9ibHWfFTt3p80Cg==", "meal"=><ActionController::Parameters {"description"=>"test"} permitted: false>, "commit"=>"Speichern", "controller"=>"meals", "action"=>"create"} permitted: false>
Or just at the meal params:
<ActionController::Parameters {"description"=>"test"} permitted: true>
If I run #meal.valid? it returns true, so I don't see where the issue is.
Also if I check the values for the hidden fields in the form, they are filled and not nil.
So why does this one parameter work, but the rest just isn't permitted even if I got them in my meal_params method?
Okay, I am dumb.
I just forgot the = for the hidden fields.
So instead of:
<% f.hidden_field :day, :value => local_assigns[:mealDay] %>
<% f.hidden_field :tip, :value => "Vollkost" %>
it should be:
<%= f.hidden_field :day, :value => local_assigns[:mealDay] %>
<%= f.hidden_field :tip, :value => "Vollkost" %>
Then everything is working.
I've looked all over and can't find a solution that works.
Relevant Controller (profits_controller.rb):
def new_tabs
#market = Market.order('mjsnumber').all.first
#profit = Profit.new
profit_types_markets_products
end
def fetch_market
#market = Market.where(:id => params[:market_id]).first
form = params["form"]
respond_to do |format|
format.js { render layout: false}
end
end
Relevant View (new_tabs.html.erb):
<%= simple_form_for #profit, :remote => true do |form| %>
<% #markets.each_with_index do |market, i| %>
<%= link_to market.nick, fetch_market_path(:market_id => market.id, :form => form, profit: #profit), :remote=>'true', :id => 'navBtn' + market.id.to_s, :class => 'd-flex flex-grow-1 align-content-center text-center nav-item nav-link ' + active(i).to_s + profit_nav_font_color(market.color).to_s, "data-toggle" => "pill", "roll" => "tab", "style" => "background-color: " + market.color.to_s + ";", remote: true %>
<% end %>
<%= render :partial => 'edit_partial_form', locals: { market: #market, form: form, profit: #profit } %>
Relevant Partial (_edit_partial_form.html.erb):
<%= form.simple_fields_for :figures, :defaults => { :input_html => { :class => "floatTextBox" }}, remote: true do |figures_form| %>
<%= figures_form.input "[test]" %>
<% end %>
Relevant JS (fetch_market.erb):
$("#edit_partial_form").html("<%= escape_javascript(render partial: 'edit_partial_form', locals: { market: #market, form: form, profit: #profit } ) %>");
Routes:
get "/fetch_market" => 'profits#fetch_market', as: 'fetch_market'
It renders the partial fine, and the links appear to contain the FormBuilder information. When I click the link and add a "puts params" to the controller, it shows the params there. But then gives me an error when loading the partial in console:
ActionView::Template::Error (undefined local variable or method `form' for #<#<Class:0x00007fdbd6453648>:0x00007fdbd68db5f8>
Did you mean? fork):
1: $("#edit_partial_form").html("<%= escape_javascript(render partial: 'edit_partial_form', locals: { market: #market, form: form, profit: #profit } ) %>");
Thanks in advance.
In fetch_market method you should to edit form = params["form"] to #form = params["form"]. You was declared local var this is why your code doesnt work. And the name of the file should be fetch_market.js.erb)
I'm new here so I hope it's appropriate for new accounts to ask questions straight away. I've been working with Rails for sometime now and usually I'm pretty good at researching and solving my own problems - but this one has had me stumped for a few days now.
I have a create action being called in a controller that contains an if-else statement that is conditional based on a check_box post parameter. I can see that the parameter is being posted to the controller so the statement should be able to correctly branch but something strange is happening. The controller executes both branches of the statement, but because my parameters are trimmed depending on that check_box, the second branch always errors out. I'm fairly confident this has little to do with routes.
Please see my code below:
Controller:
def create
#quote = Quote.find(params[:quote_id])
if params[:quote_item][:custom] == 1
#quote_item = #quote.quote_items.new(quote_item_params)
#quote_item.rate = nil
#quote_item.custom = true
#quote_item.unit_price = params[:quote_item][:unit_price]
#quote_item.rate_name = params[:quote_item][:title]
else
#quote_item = #quote.quote_items.new(quote_item_params)
#quote_item.custom = false
#rate = Rate.find(params[:quote_item][:rate_id])
#quote_item.unit_price = #rate.price
#quote_item.rate_name = #rate.product.name
end
respond_to do |format|
if #quote.save
format.html { redirect_to #quote, notice: 'Quote item was successfully added.' }
format.json { render :show, status: :created, location: #quote }
else
format.html { redirect_to #quote }
format.json { render json: #quote.errors, status: :unprocessable_entity }
end
end
View:
<% if #rates.any? %>
<%= bootstrap_form_for([#quote, #quote_item], layout: :inline) do |f| %>
<%= f.text_field :title, class: 'input-sm', hide_label: true, :placeholder => "Line Title" %>
<%= f.select(:rate_id, #rates.collect {|r| [r.select_summary_text, r.id ] }, { hide_label: true }, { :class => 'input-sm' }) %>
<%= f.number_field :quantity, class: 'input-sm', hide_label: true, :min => 1, :length => 2, :value => 1 %>
<%= f.text_field :unit_price, class: 'input-sm', hide_label: true, :min => 1, :length => 2, :prepend => "$" %>
<%= f.text_field :note, class: 'input-sm', hide_label: true, :placeholder => "Note (Optional)" %>
<%= f.submit "Add Quote Item", class: 'btn btn-sm btn-default' %>
<%= f.check_box :custom, label: "Override" %>
<% end %>
<% else %>
<div class="well well-sm"><i class="fa fa-usd"></i> No pricing for this customer has been set. Set product pricing for this jobs customer <%= link_to user_path(#quote.job.user) do %>here.<% end %></div>
<% end %>
My create method errors out here:
#rate = Rate.find(params[:quote_item][:rate_id])
With:
ActiveRecord::RecordNotFound (Couldn't find Rate with 'id'=):
app/controllers/quote_items_controller.rb:19:in `create'
This error is correct though, because the rate ID isn't processed in first branch of the if-else statement. I've tried different check_box form fields, directly overriding 'custom' and both branches still run.
Any help here is greatly appreciated!
The params hash contains strings.
Try swapping:
if params[:quote_item][:custom] == '1'
for your original:
if params[:quote_item][:custom] == 1
I'm using the Twitter Bootstrap framework within a Rails app. What I'm trying to work out is how to render the errors within the window and not have a page reloaded.
Below is an example:
#modalEvent.modal.hide
.modal-header
%button.close{"data-dismiss" => "modal", :type => "button"} ×
%h3 Schedule Form
= form_for(#schedule, :html => { :class => "form-horizontal"}) do |f|
.modal-body
- if #schedule.errors.any?
#notice.alert.alert-error
%button.close{"data-dismiss" => "alert"} ×
%strong Error:
= pluralize(#schedule.errors.count, "error")
prohibited #{event_display(#schedule.event)} from being
saved:
%ul
- #schedule.errors.full_messages.each do |msg|
%li= msg
.widget-content.nopadding
.control-group
= f.label :event_type, :class =>'control-label'
.controls
= f.select :event, Schedule::EVENT_TYPES
#3{:style => 'display:none'}
.control-group
= f.label :name, :class =>'control-label'
.controls
= f.text_field :result_id, :class => "required error"
.control-group
= f.label :date_and_time, :class =>'control-label'
.controls
= f.text_field :time, :class => "datepicker", :required => :required, :type => :datetime, "data-date-format" =>"dd/mm/yyyy"
.control-group
= f.label :duration, :class =>'control-label'
.controls
.input-append
= f.number_field :duration, :placeholder => 'Time in Minutes', :required => :required
%span.add-on
%i.icon-time
%span.help-block Duration of event in minutes
.control-group
= f.label :arrival_time, :class =>'control-label'
.controls
.input-append
= f.number_field :arrival_time, :placeholder => 'Time in Minutes', :required => :required
%span.add-on
%i.icon-time
%span.help-block Time in minutes before event
.control-group
= f.label :location, :class =>'control-label'
.controls
= select("schedule", "location_id", Location.all.collect { |p| [p.name, p.id] }, {:include_blank => 'None'})
.control-group
= f.label :players, :class =>'control-label'
.controls
= select(:schedule, :selected_players, #players.map { |p| [full_name(p), p.id] }, {:include_blank => false}, "data-placeholder" => 'Add Players to Lineup', :prompt => 'Add Players to Lineup', :multiple => "multiple")
#1{:style => 'display:block'}
-if current_user.admin?
.control-group
= f.label :team, :class =>'control-label'
.controls
= select("schedule", "team_id", Team.all.collect { |p| [p.name, p.id] }, {:include_blank => 'None'})
- else
=f.hidden_field :team_id, :value => current_user.team_id
.control-group
= f.label :opponent, :class =>'control-label'
.controls
= select("schedule", "opponent_id", Opponent.all.collect { |p| [p.name, p.id] }, {:include_blank => 'None'})
.control-group
= f.label :home_or_away, :class =>'control-label'
.controls
= f.select :home_or_away, Schedule::HOME_OR_AWAY, {:include_blank => 'None'}
.modal-footer
= f.submit 'Save Event', :class => 'btn btn-primary'
%a.btn.btn-danger{"data-dismiss" => "modal", :href => "#"} Cancel
controller
def create
#schedule = Schedule.new(params[:schedule])
#user = User.find(current_user)
#players = User.where(:team_id => current_user[:team_id]).all
respond_to do |format|
if #schedule.save
Notifier.event_added(#user,#schedule).deliver
format.html { redirect_to(schedules_url,
:notice => "#{event_display_c(#schedule.event)} vs #{#schedule.opponent.name} was successfully created.") }
format.json { render :json => #schedule, :status => :created, :location => #schedule }
else
format.html { render :action => "new" }
format.json { render :json => #schedule.errors, :status => :unprocessable_entity }
end
end
end
If you want to avoid page reload and still show server provided error messages you have to use AJAX somehow. I think there is still no one right way of doing it. You should start by googling PJAX. Another thing you should learn about is Rails provided unobtrusive JavaScript
Also I would recommend you try out is simple_form gem, which has nothing to do with AJAX but it would simplify your views ;)
I do something very similar to this albeit using PHP/CodeIgniter instead of Rails, but I use bootstrap within a modal window for this.
What I do is, upon form submit, I ajax the form data to a process script which then validates the data - if the validation fails, it returns (via a JSON object) the control-group class containing the error and an optional error message to display. If the validation succeeds, it performs whatever action you hoped to perform and simply returns a "success" flag that signals the program to display a success message and close the modal.
Let me know if this sample is of any use to you. If it is, I can provide a sample of the server-side validation and output I perform, although it would not be in ruby.
Here's a sample form, in the bootstrap modal format:
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h3>Add Something</h3>
</div>
<div class="modal-body">
<form id="your_form">
<fieldset class="control-group id-group">
<label>Some ID:</label><input type="text" name="SomeID" /><div class="clear"></div>
<label>Another ID:</label><input type="text" name="Another ID" /><div class="clear"></div>
</fieldset>
<fieldset class="control-group category-group">
<label>Category 1:</label><input type="text" name="Cat1" /><div class="clear"></div>
<label>Category 2:</label><input type="text" name="Cat2" /><div class="clear"></div>
</fieldset>
<fieldset class="control-group description-group">
<label>Description:</label><input type="text" name="Description" /><div class="clear"></div>
</fieldset>
<div class="clear"></div>
</form>
<div class="clear"></div>
<div class="alert alert-error" id="addError">
</div>
<div class="alert alert-success" id="addSuccess">
</div>
</div>
<div class="modal-footer">
Cancel
Save
</div>
Here's a sample of my javascript call upon form submit:
$("#saveSomethingButton").click(function() {
//hide any previous errors should this be a second submit
$(".alert").hide();
$(".control-group").removeClass('error');
$("input").blur();
//set the save button to a "please wait" state to prevent double submission
$("#saveSomethingButton").button('loading');
//post to the validation script - if
$.post("<?php echo base_url();?>somecontroller/processfunction", $("#your_form").serialize(), function(data) {
//if we were successful, show the happiness message and close the modal
if (data.success == 1) {
$("#addSuccess").html("<strong>Success! </strong> Something successfully added to database.");
$("#addSuccess").fadeIn(300, function() {
setTimeout(function() {
$("#someModal").modal('hide');
}, 2000);
});
//otherwise, highlight the problem fields and display the error
} else {
$("#addError").html("<strong>Error: </strong> "+data.message);
$("."+data.fieldset).addClass("error");
$("#addError").fadeIn(300);
$("."+data.fieldset+":first input:first").focus();
}
//reset the button state so that they can correct errors and submit again
$("#saveSomethingButton").button('reset');
}, "json");
//return false to prevent default form submission, which would reload the page
return false;
});
I'm trying to pass a string with a link_to_remote call as the :id, and the string should be collected from an input field with and id of "movie_title".
<div id="search_list">Nothing here yet</div>
<br />
<% semantic_form_for #movie do |f| %>
<% f.inputs do -%>
<%= f.input :title, :class => "movie_title" %> <%= link_to_remote( 'Search...', { :url => { :action => :imdb_search, :id => "'+$('\#movie_title').value+'" } }, { :title => "Search for this movie", :class => "imdb_search" } ) -%>
[...removed text that does not matter...]
<% end -%>
<%= f.buttons %>
<% end %>
I keep getting an javascript error, and if I remove the # from the jquery in the link, it returns "Undefined".
The link I get is:
<a class="imdb_search" href="#" onclick="jQuery.ajax({data:'authenticity_token=' + encodeURIComponent('yHPHYTZsPTQLi9JYSauUYcoie/pqPPk2uHBTN0PzNsQ='), dataType:'script', type:'post', url:'/movies/imdb_search/'+$('%23movie_title').value+''}); return false;" title="Search for this movie">Search...</a>
So I want the link updated with the contents of movie_title.
How do I do that?
I'd try something like
<%= link_to_remote( 'Search...', {
:url => { :action => :imdb_search},
:with => "'id=' + $('movie_title').value",
{:title => "Search for this movie", :class => "imdb_search"}
)
Fixed it
Used:
$('movie_title').val()
Insted of
$('movie_title').value