I've got a web page that creates an "Activity." Using simple form, the user fills in some basic information about the activity:
#new.html.erb
<%= simple_form_for #activity do |f| %>
<%= f.input :name, label: "Activity name", input_html: { id: "create_activity_name_field" }, hint: "At least 7 characters" %>
<%= f.input :type, label: "Activity type", input_html: { id: "create_activity_type_field" }, as: :select, :include_blank => false, collection: Activity.types.keys.to_a %>
<%= f.input :date_field, input_html: { id: "create_activity_date_field" }, as: :string %>
<%= f.input :time_field, input_html: { id: "create_activity_time_field" }, as: :string %>
<% f.button :submit, :class => "btn btn-primary" -%>
<% end %>
However, I am also using MapBox to display a map. The user will click the points on the map to make a route for the activity. When the user is done drawing the route, I can then extract GeoJSON data for this route.
I would like to use javascript to POST the GeoJSON data with the form data. On the backend, I'll have rails convert the GeoJSON to a KML file, and use Paperclip to upload it to Amazon S3. The rest of the data can be saved, and Paperclip will give me a URL to the KML file which I can associate with the activity.
I'm a Rails noob, and I can't figure out how to do this nor track down anything to get me over this hurdle. I considered using javascript's FormData. I was very attracted to this approach, because implementation looks so simple, but apparently it can only really handle key/value pairs, not complex nested JSON data.
Any suggestions or strategies greatly appreciated. And greatly appreciated++ if anyone can give detailed answers because, like I said, I'm a few weeks fresh to rails and web development.
What you probably want to do here is to prevent the default behavior of your form using in JS :
$('form').submit(function(e) {
e.preventDefault();
var form = $('form');
var form_data_json = JSON.stringify(form.serializeArray());
form_plus_geojson = form_data_json.concat(Geojson);
Grab your data in JS ( this should be easy but you dont really say how do you extract the data from mapbox ).
Then send back the data via AJAX ( don't forget validation on either end )
if (my_data_is_not_good/empty/whatever) {
console.log('nop')
} else {
$.ajax({
type: 'post',
url: Yourcontrollerpostaction,
data: form_plus_geojson,
dataType: "JSON",
Once the data has been sent you want to resume the normal form submission post behavior and submit the form.
complete: function() {
this.off('submit');
this.submit();
}
});
}
});
Then you just need to parse the data in your controler.
def post_controller
if request.xhr?
my_hash = JSON.parse(params[:mydata])
else
normal post behavior
end
end
Everything is quick pseudocode you might have to fix some stuff as i haven't tested it but it should work.
Obviously now that we are doing everything in ajax you don't need the complet function call back and can remove that part.
Kudos to #jDay for pointing me in the right direction -- specifically with concatenating the JSON data. However, I couldn't get his specific solution to work on my app, and I went trying another method which, in my view, was a little more straightforward.
I used the Rails simple_form_for gem to make the form, specifying a remote post call:
#new.html.erb
<%= simple_form_for #activity, :url => '/activities', :method => :post, :remote => true, html: {id: :activity_create_form, "data-type" => :json} do |f| %>
<%= f.input :name, label: "Activity name", input_html: { id: "create_activity_name_field" }, hint: "At least 7 characters" %>
<%= f.input :type, label: "Activity type", input_html: { id: "create_activity_type_field" }, as: :select, :include_blank => false, collection: Activity.types.keys.to_a %>
<%= f.input :date_field, input_html: { id: "create_activity_date_field" }, as: :string %>
<%= f.input :time_field, input_html: { id: "create_activity_time_field" }, as: :string %>
<%= f.button :submit, :class => "btn btn-primary", input_html: {id: "create_activity_submit_btn"} -%>
<% end %>
Then I used jQuery to to hijack the form submit button and append an invisible form element bearing the GeoJSON data. The data, however, had to be stringified so it could be sent:
#activity_new.js
$("form").submit( function (e) {
e.preventDefault();
$('<input />')
.attr('type', 'hidden')
.attr('name', 'routeGeoJson')
.attr('value', JSON.stringify(polyline.toGeoJSON()))
.appendTo(this);
return true;
});
(Here, "polyline" is a Leaflet L.polyline object that I used to draw on the map.)
In the controller, you'll get params like this:
{"utf8"=>"✓", "activity"=>{"name"=>"A jaunt in the woods", "type"=>"walk",
"date_field"=>"2015-09-08", "time_field"=>"07:00"},
"routeGeoJson"=>"{\"type\":\"Feature\",\"properties\":{},\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-118.38855654001236,33.95361274499209],[-118.36624056100845,33.97681937760982]]}}",
"commit"=>"Create Activity", "controller"=>"activities",
"action"=>"create"}
Since the "routeGeoJson" data is still in string form, I reassembled the GeoJSON data by parsing it using the JSON gem (which I believe is included by default in Rails 4):
def create
geojson = JSON.parse(params[:routeGeoJson])
# do something...
end
And you get your GeoJSON hash:
{"type"=>"Feature", "properties"=>{},
"geometry"=>{"type"=>"LineString",
"coordinates"=>[[-118.42014223337173, 33.98407904797006],
[-118.37825685739517, 33.956175751601826]]}}
Related
I had this working, but must have made a minor change somewhere that's stopped it working.
I have a call to Javascript when clicking on a button, but when I click the button nothing loads. I've gone over all parts of the code multiple times but cannot see where the failure is happening and there's no feedback in the logs so I have nothing to go on.
As I said it was working previously, but then stopped.
This is my button:
<%= button_tag t(".change"), class: "btn let-admin-change",
data: { title: t(".confirm_switch"),
cancel: t(".cancel"),
cta: t(".confirm_cta"),
plot: plot.id } %>
And my javascript file:
(function (document, $) {
'use strict'
$(document).on('click', '.let-admin-change', function (event) {
var dataIn = $(this).data()
var $changeContainer = $('.change-admin-letting-form')
$('body').append($changeContainer)
var $form = $('.edit_plot')
$changeContainer.dialog({
show: 'show',
modal: true,
width: 700,
title: dataIn.title,
buttons: [
{
text: dataIn.cancel,
class: 'btn',
click: function () {
$(this).dialog('destroy')
}
},
{
text: dataIn.cta,
class: 'btn-send btn',
id: 'btn_submit',
click: function () {
$.ajax({
url: '/plots/' + dataIn.plot,
data: $form.serialize(),
type: 'PUT'
})
$(this).dialog('destroy')
$changeContainer.hide()
}
}]
}).prev().find('.ui-dialog-titlebar-close').hide() // Hide the standard close button
})
})(document, window.jQuery)
And the form it's calling:
<div class="change-admin-letting-form">
<%= simple_form_for plot, url: phase_lettings_path(#phase), remote: true do |f| %>
<div>
<%= f.hidden_field :letter_type, value: :homeowner %>
<%= f.hidden_field :letable_type, value: :planet_rent %>
</div>
<div>
<h3><%= t(".are_you_sure", plot: plot) %></h3>
<p><%= t(".confirm_admin_swap").html_safe %></p>
</div>
<% end %>
</div>
Can anyone see where this is going wrong to stop the javascript from loading?
EDIT
If I add
<%= render "change_admin_letting_form", plot: plot %>
underneath the button then the form loads (but it loads multiple forms - one form for each instance of the button / each plot on the page), so I assume the issue is that the plot information is not being passed to the form? I'm not sure how to pass this information across.
I've managed to get this working by doing the following to get the correct information to the form:
I've changed the class of the form to take a variable:
<div class="hidden <%= plot_id %>">
And send the variable to the form by rendering the form from inside the block that loads each plot:
<% plots.each do |plot| %>
// some omitted code
<%= button_tag t(".change"), class: "btn let-admin-change",
data: { title: t(".confirm_switch"),
cancel: t(".cancel"),
cta: t(".confirm_cta"),
plot: plot.id } %>
<%= render "change_letter_type_form", plot_id: "change_letting_plot_#{plot.id}", plot: plot %>
<% end %>
And changed the part of the javascript that loads the form to search for the new plot-based class:
var $changeContainer = $('.change_letting_plot_' + dataIn.plot)
So now only the one form relevant to the selected plot loads, and the form works as required.
I have next to zero experience with javascript, so this probably isn't the 'proper' way to get this to work, but it works so I'll take it as it is.
I am using a sidebar that searches and generates a list (within the sidebar) I want the main page to remain unchanged when searching with the sidebar. I believe this requires some JS, but I know nothing about JS.
my navbar is in a div _navbar.html.erb
main page is basically any other page being generated
here is my code: https://github.com/nrkfeller/ratingapplication
You need to use AJAX with rails. Here is how it may work for you:
Add a :remote => true to your form and :'data-update-target' => 'update-container' to specify where you want the search results to go. You might want to avoid using the courses_path, but use form_tag({:controller => courses, :action => 'search'} to directly state where you want the form to be submitted.
<%= form_tag courses_path, method: :get, :remote => true ,class: "navbar- form navbar-right", :'data-update-target' => 'update-container' , role: "search" do %>
<p>
<%= text_field_tag :search, params[:search], class: "form-control" %>
<%= submit_tag "Search",:disable_with => 'Please wait...', name: nil, class: "btn btn-default" %>
</p>
<% end %>
Add this to your sidebar. This is where the partial with the results will come:
<div id="update-container"></div>
Add the javascript`` to put it where it is supposed to be when the request finishes:
<script>
$(function() {
$('form[data-update-target]').on('ajax:success', function(evt, data) {
var target = $(this).data('update-target');
$('#' + target).html(data);
});
});
Add a partial named _search_results.html.erb <- this is where your results go.
<!-- arbitrary code -->
<%= #results.each do |result| %>
<%= result %>
In your controller:
def search
#results= #your search code
render :partial => 'search_results', :content_type => 'text/html'
end
This is will get the functionality you wanted. Beware, this is more of a pesudocode than an exact implementation of what you want to do. You have to fill in the gaps.
I hope I was helpful!
I am looking to update a url when a selection is made from a dropdown. I would like to have the query to be dynamic, here is the following code:
<select id="mySchool" onchange="this.form.submit()">
<% #schools.each do |school| %>
<option value="<%= school.id %>"><%= school.name %></option>
<% end %>
</select>
<%= link_to "Apply School", "schools/assign_users?user_id=#{#user.id}&school_id=", :class => "btn btn-primary", :type => "button" %>
Can anyone point me in the right direction?
This is not the best way to create select in rails. You should rather use rails select_tag helper like this:
<%= select_tag 'school_id', options_for_select(#schools.collect{ |s| [u.name, u.id] }), id: "mySchool" %>
I am looking to update a url when a selection is made from a dropdown.
I think instead of showing the link upfront you should show the link only when a user select a value from dropdown so your code should be something like this:
<%= select_tag 'school_id', options_for_select(#schools.collect{ |s| [u.name, u.id] }), id: "mySchool" %>
<div id="schoolLink"></div>
#_link.html.erb
<%= link_to "Apply School", "schools/assign_users?user_id=#{user.id}&school_id=#{school.id}", :class => "btn btn-primary", :type => "button" %>
Now make a route to which you want to send the ajax request to:
post 'selected_school/:id' => 'school#selected', as: "select_school"
write a js function which will send ajax request on changing values in dropdown
$(document).on("change","#mySchool",function(e){
var school_id = $(this).val();
$.ajax({
type: "POST",
url: "/selected_school",
data: {id : school_id }
});
});
Find school and user inside controller and then finally render link by js
#school_controller.rb
def selected
#school = School.find(params[:id]) # find school by the passed id
#user = current_user # your logic to find user
end
#app/views/school/selected.js
$("#schoolLink").html("<%=j render partial: 'link', locals: {user: #user, school: #school} %>");
For details checkout Working with Javascript in Rails
I am trying to upload files using a Rails form where the remote is set to true. I'm using Rails 4.1.1. Let's say that my model is a Message, and it is using JavaScript so that the user could easily send multiple messages without reloading the page. The form is set like this:
<%= form_for #message, url: {action: "create"}, html: {:class => "message-form", multipart: true}, remote: true do |f| %>
The user can upload images with the Message, if they wish to do so. MessageImage acts as a nested attribute in the form, and is declared like this (http://railscasts.com/episodes/196-nested-model-form-revised way):
<%= f.fields_for :message_images do |builder| %>
<%= render 'message_image_fields', f: builder %>
<%= link_to_add_fields "Add an image", f, :message_images %>
<% end %>
On my controller the action is roughly like this:
if #message.save
flash.now[:success] = "Message sent"
else
flash.now[:alert] = "Error sending the message"
end
respond_to do |format|
format.html { render 'new' }
format.js { render 'new' }
end
Now, this works perfectly as long as the user doesn't send any images, but if they do, it uses format.html instead of format.js. Removing the format.html gives ActionController::UnknownFormat-exception.
Now, this obviously has to do with the fact that you can't submit files with remote set to true. I tried searching a bit, and found this gem https://github.com/JangoSteve/remotipart , which seems to be exactly what I'm looking for. I installed it following the instructions, but for some reason it still doesn't work and gives ActionController::UnknownFormat-exception if I remove the format.html. However, I couldn't find any example of it involving nested attributes. Are there any alternatives for this gem or any other way to fix this, or should I just set that it renders HTML if the user submits files?
JQuery
I don't know how to get the nested model aspect of this, but we've done file uploading with JQuery / asynchronicity before here (register for account, log into profile):
We used the jquery-file-upload gem - basically allowing you to pass the files through Ajax to your controller backend. To give you a clear idea of how we did this:
--
Code
#app/assets/javascripts/application.js
$('#avatar').fileupload({
url: '/profile/' + $(this).attr('data_id'),
dataType: 'json',
type: 'post',
add: function (e, data) {
$(this).avatar_loading('avatar_loading');
data.submit();
},
success: function (data, status) {;
$("#avatar_img").fadeOut('fast', function() {
$(this).attr("src", data.avatar_url).fadeIn('fast', function(){
$(this).avatar_loading('avatar_loading');
});
});
}
});
#app/views/users/index.html.erb
<%= form_for :upload, :html => {:multipart => true, :id => "avatar"}, :method => :put, url: profile_path(current_user.id), "data_id" => current_user.id do |f| %>
<div class="btn btn-success fileinput-button avatar" id="avatar_container">
<%= f.file_field :avatar, :title => "Upload New" %>
<%= image_tag(#user.profile.avatar.url, :width=> '100%', :id => "avatar_img", :alt => name?(#user)) %>
</div>
<% end %>
#app/controllers/profile_controller.rb
Class ProfileController < ApplicationController
def update
def update
#profile = User.find(current_user.id)
#profile.profile.update(upload_params)
respond_to do |format|
format.html { render :nothing => true }
format.js { render :partial => 'profiles/update.js' }
format.json {
render :json => #profile.profile.as_json(:only => [:id, :avatar], :methods => [:avatar_url])
}
end
def upload_params
params.require(:upload).permit(:avatar, :public, :description)
end
end
end
--
Implementation
For your implementation, I would recommend firstly creating the message, and then getting the user to append some images to it in another action
After you've got that working, you could get it to work as one form
I would like to carry out a validation before saving by determining if a User has filled in a particular field, the Payment amount field below and chosen status = "Closed" before submitting the form. If he does one without the other then the form should not save
Edit page
<%= simple_form_for #invoice, :html => { :class => 'form-horizontal' } do |f| %>
<%= render "shared/error_messages", :target => #invoice %>
<%= f.association :customer, disabled: #invoice.persisted? %>
<%= f.input :due_date, as: :string, input_html: { class: "datepicker" }, disabled: #invoice.persisted? %>
<%= f.input :invoice_date, as: :string, input_html: { class: "datepicker" }, disabled: #invoice.persisted? %>
<%= f.input :payment_method, as: :select, :collection => [['Cash','Cash'],['Cheque','Cheque'],['In-House transfer','In-House transfer'],['Account Ledger','Account ledger']], :selected => ['Cash','Cash'] %>
<%= f.input :reference_no, :label => 'Payment Reference No', as: :string %>
<%= f.input :amount, as: :string %>
<%= f.input :payment_date, as: :string, input_html: {class: "datepicker"} %>
<%= f.input :status, as: :select, collection: Invoice::VALID_STATUS %>
VALID_STATUS = [ 'Draft', 'Open', 'Closed', 'Void' ] in Invoice.rb
I would like that if the user changes the Status to Closed he should have entered an amount in the form. A user should not be able to change status to closed without entering an amount
In the model (app/models/invoice_model.rb) put
validate :close_must_have_amount
Then define it (same file)
def close_must_have_amount
:status == 'closed' && :amount # May need to tweak this
end
To have the model level validations applied client side you can use
https://github.com/bcardarella/client_side_validations/
1) Javascript Form Validation is generally done by names.
function ValidateForm(){
var form = document.forms['myForm'];
if ((form['status'].value == "Closed") && !(form['amount'].value)){
alert("You gave a 'Closed' status value, but did not provide an amount, please rectify this problem!");
return(false);
} else {
return(true);
}
}
And then:
<%= simple_form_for #invoice, :onsubmit => "ValidateForm();", :html => { :class => 'form-horizontal', :name => 'myForm' } do |f| %>
<%= f.input :amount, :html => { :name => 'amount'}, as: :string %>
<%= f.input :status, as: :select, :html => { :name => 'status'}, collection: Invoice::VALID_STATUS %>
A brief walkthrough onSubmit triggers when a form is submitted, but before it is actually posted to the server.
A javascrtipt function that is trigered by an event and terminates with return(false); will immediately terminate the event, while return(true); (or pretty much anything else really) makes the event continue as planned.
Finally, be aware that relying exclusively on client side validation is a terrible idea, as a determined user could do something like:
1) Make a perfectly legitimate submission with firebug open and inspect the headers etc.
2) Craft their own HTTP request containing bogus/bad data.
3) Submit it through any one of the myriad HTTP tools.
Clientside Validation is a "nice to have".
Serverside Validation is a "must have".
If you want to do it in client side:
<script>
$(document).ready(function(){
$('#status').change(function(){
if($(this).val() == "Closed" && ($('#amount').val() == null || $('#amount') == "")){
alert("Amount must be needed when status is closed")
}
});
});
</script>