I have a view containing two select boxes: company and employee. Both have a blank option and when a company is selected, it populates the employees based on the selected company. This works just fine. My issue is that when I submit a form that fails validation (as expected) and I select a company once more once the 'new' view renders again in extensions#create, my 'get' AJAX call has changed from /servers/1/extensions/get_company_employees (correct) to /servers/1/get_company_employees (incorrect) and is returning 404 Not found. Why is this happening and what should I do to remedy this? All relevant code is listed below
routes.config
resources :servers do
scope module: 'servers' do
resources :extensions, shallow: true
end
end
# Ajax call
get 'servers/:id/extensions/get_company_employees', to: 'servers/extensions#get_company_employees', as: 'get_company_employees'
app/controllers/servers/extensions_controller.rb
class Servers::ExtensionsController < ApplicationController
def get_company_employees
#server = Server.find(params[:id])
#extension = #server.extensions.build
#path = [#server, #extension]
#companies = Company.all
#employees = Employee.where("company_id = ?", params[:company_id])
respond_to do |format|
format.js
end
end
def new
#server = Server.find(params[:server_id])
#extension = #server.extensions.build
#path = [#server, #extension]
#companies = Company.all
#employees = Employee.none
end
def create
#server = Server.find(params[:server_id])
#extension = #server.extensions.build(extension_params)
#extension.password = "pass"
if #extension.save
flash[:success] = "Successfully created extension"
redirect_to #extension
else
#path = [#server, #extension]
#companies = Company.all
#employees = Employee.none
flash.now[:error] = "Failed to create extension"
render "new"
end
end
private
def extension_params
params.require(:extension).permit(:value, :password, :employee_id, :server_id, :phone_id)
end
end
app/views/servers/extensions/_form.html.erb
<%= form_for(#path) do |f| %>
<p>
<%= label_tag(:company) %>
<%= select_tag "company", options_from_collection_for_select(#companies, "id", "name"), include_blank: "Select a company" %>
</p>
<p>
<%= f.label(:employee) %>
<%= f.collection_select :employee_id, #employees, :id, :full_name, include_blank: "Select an employee" %>
</p>
<p>
<%= f.submit "Submit" %>
</p>
<% end %>
app/views/servers/extensions/get_company_employees.js.coffee
$("#extension_employee_id").empty()
.append("<option>Select an employee</option>")
.append("<%= j options_from_collection_for_select(#employees, :id, :full_name) %>")
app/assets/javascripts/servers/extensions.coffee
$ ->
$(document).on 'page:load', '#company', (evt) ->
$.ajax 'get_company_employees',
type: 'GET'
dataType: 'script'
data: {
company_id: $("#company option:selected").val()
}
$(document).on 'change', '#company', (evt) ->
$.ajax 'get_company_employees',
type: 'GET'
dataType: 'script'
data: {
company_id: $("#company option:selected").val()
}
Its because you have now specified complete URL in ajax call
It should be something like this in both cases.
$.ajax "/servers/"+ id +"/extensions/get_company_employees',
type: 'GET'
dataType: 'script'
data: {
company_id: $("#company option:selected").val()
}
// store and fetch id attribute from page in any of the dom element
Ideally you should write a function for your ajax call which can be called wherever required and code redundancy can be reduced.
Related
I am building an application where i try to suggest "Similar topics" in rails, as the user put the title of his/her new story.
I have 2 problems:
The controller with the custom action does not work at all. it seems that the server simply retrieves the view. Without running any of the code in the action
To go around the issue of the controller, i created a service.rb with a function to retrieve the records based on the params[:title], but from here I do NOT know how to make small popup window with suggestions (and weblinks) as the user write the title of the topic.
I have done so far :
View
<div class="col-md-12">
<%= simple_form_for #message do |f| %>
<div style="font-size: xx-small; font-style: italic; color: #44B5EB">
<%= f.input :title, label: "#{t :Title}", placeholder: "#{t :Search}", id: "title" , data: {behavior: "autocomplete_message"}%>
<%= f.cktext_area :description, label: "#{t :Message_body}", :input_html => {:rows => 25} %>
<br> <br>
<%= f.submit "#{t :Create_story}", class: "btn btn-primary"%>
<% end %>
</div>
</div>
<script>
$("#title").addEventListener("turbolinks:load", function () {
$input = $("[data-behavior = 'autocomplete_message']");
var options = {
getValue: "name",
url: function (phrase) {
return "messages/search.json?title=" + phrase;
},
categories: [
{
listLocation: "qandas",
header: "<p class='Search_drop_separate'>Q&A </p>",
}
],
list: {
onChooseEvent: function(){
var url = $input.getSelectedItemData().url;
$input.val("");
Turbolinks.visit(url)
}
}
};
$input.easyAutocomplete(options)
});
</script>
Controller
class StorytController < ApplicationController
before_action :authenticate_user!
before_action :find_message, only: [:show, :edit, :update, :destroy]
respond_to :html, :js
...
def search
##qandasquestions = Qandasquestion.ransack(question_or_answer_cont: params[:q]).result(distinct: true)
respond_to do |format|
format.html {
#qandasquestions = #qandasquestions
redirect_to stories_search_path
}
format.json {
#qandasquestions = #qandasquestions.limit(5)
}
end
end
def full_name
"#{first_name} #{last_name}"
end
private
def force_json
request.format = :json
end
end
Search.jason.builder
json.qandas do
json.array!(#qandasquestions) do |qandasquestion|
json.name "#{qandasquestion.question}"
json.url qanda_path(qandasquestion.qanda_id)
end
end
routes:
get 'stories/search'
What I am looking to build is actually very similar to what we have on Stackoverflow on the principle.
Anybody did something similar and can help me please?
I don't mean to sidetrack you but if you have a couple minutes to check this out, have you seen select2? It works nice with Rails and there's also a gem to make it work nice with simple_form
https://github.com/lndl/select2_simple_form
In Rails 5 app with devise, I need to use a new.js.erb file to update select tag in my registrations view and controller. I cant seem to figure out why my new.js.erb file isn't working.
I've tried to use respond_to in controller as below,
registrations-controller.rb
def new
super
#cities = CS.get(:us,params[:state])
respond_to do |format|
format.js { render '/new.js.erb' }# layout: false }
format.html
end
end
new.html.erb
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), :remote => true) do |f| %>
<div class="signup-input-container">
<div class="field">
<%= f.text_field :firstname, autofocus: true, autocomplete: "firstname", placeholder: "First name", class: "signup-input-container--input" %>
</div>
<div class="field">
<%= f.select :state, options_for_select(CS.states(:us).map { |code, name| [name, code] }),{:prompt => "State"}, {:class => "signup-input-container--input", :id => "state-picker"} %>
</div>
<div class="field">
<%= f.select :city, options_for_select([]),{}, {:class => "signup-input-container--input", :id => "city-picker"} %>
</div>
</div>
<% end %>
new.js.erb
var city = document.getElementById("city-picker");
while (city.firstChild) city.removeChild(city.firstChild);
var placeholder = document.createElement("option");
placeholder.text = "Choose a city";
placeholder.value = "";
city.appendChild(placeholder);
<% #cities.each do |c| %>
city.options[city.options.length] = new Option('<%= c %>');
<% end %>
main.js
var state = document.getElementById("state-picker");
state.addEventListener("change", function() {
$.ajax({
url: "/states?state=" + state.value,
type: "GET"
})
})
I'm expecting this to create select tag options with my array of cities in my controller. Does anyone know how to get this to work?
To solve this you should just setup a separate controller where you can fetch the data from asynchronously and alternatively there are also several free API's which can be used for geographical lookup such as Googles Geocoding API and Geonames.
To setup a separate controller you can do it by:
# /config/routes.rb
get '/states/:state_id/cities', to: 'cities#index'
# /app/controllers/cities_controller.rb
class CitiesController < ApplicationController
# GET
def index
#cities = CS.get(:us, params[:state_id])
respond_to do |f|
f.json { render json: #cities }
end
end
end
I would skip using a .js.erb template altogether and just return JSON data which you can use directly in your JS or with one of the many existing autocomplete solutions. .js.erb only makes sense for extensive HTML templating (like for example rendering an entire form) where you want to reuse your server side templates - it greatly increases the complexity and generally makes a mess of your javascript which is not worth it just to output a list of option tags.
// If you are using jQuery you might as well setup a delegated
// handler that works with turbolinks,
$(document).on('change', '#state-picker', function(){
$.getJSON("/states/" + $(this).value() + "/cities", function(data){
// using a fragment avoids updating the DOM for every iteration.
var $frag = $('<select>');
$.each(data, function(city){
$frag.append$('<option>' + data + '</option>');
});
$('#city-picker').empty()
.append($('frag').children('option'));
});
});
Think of the below as a bike rental. Someone fills out a form and gets a bike assigned to them which they can rent and borrow for a certain amount of time.
The problem I am having is I am trying to show the person who wants to rent the bikes what bikes are available before they submit the form. Below is my attempt using ajax. I have no errors but also my select is not updating.
request controller methods below
def new
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
#new_request = Request.new
end
create method below (with a temporary workaround, that reloads the form with a warning about availability.)
def create
#request = Request.new(request_params)
available_bikes = #request.new_request(current_user.id)
if (available_bikes >= #request.number_of_bikes_wanted) && #request.save
redirect_to root_path
else
flash[:warning] = "You have requested more bikes than available. There are only #{available_bikes} bikes available"
redirect_to new_request_url
end
end
params in request controller
def request_params
params.require(:request).permit(:Borrow_time, :Borrow_date,
:Return_date, :Return_time,
:number_of_bikes_wanted, bike_ids: [])
end
new.html.erb view
<div class="form" align = "center">
<%= render 'form.js.erb' %>
</div>
_form.js.erb below
<script type="text/javascript">
$(document).ready(function() {
$('.my-date').on('change', function() {
var data = {}
$('.my-date').each(function() {
if($(this).val()) {
data[$(this).attr("id")] = $(this).val();
}
});
if(Object.keys(data).length > 1) {
$.ajax({
type: "POST",
url: <%= new_request_path %>,
data: data
});
}
});
});
var options = "";
<% #bikes.each do |bike| %>
options += "<option value='<%= bike.id %>'><%= bike.name %></option>"
<% end %>
$('#request_number_of_bikes_wanted').html(options);
</script>
<div class="block-it" align=center>
<br>
<%= form_for #new_request do |request| %>
<%= request.label :Borrow_date, 'Borrow on' %>
<%= request.date_field :Borrow_date, id: 'Borrow_date', class: 'my-date', min: Date.today, :required => true %>
<%= request.label :Borrow_time, 'Borrow at' %>
<%= request.time_field :Borrow_time, value: '10:00', min: '9:00 AM', max: '4:30 PM', default: '10:00 AM', :ignore_date => true, :required => true %>
<br><br>
<%= request.label :Return_date, 'Return On' %>
<%= request.date_field :Return_date, id: 'Return_date', class: 'my-date', min: Date.today, :required => true %>
<%= request.label :Return_time, 'Return at' %>
<%= request.time_field :Return_time, value: '10:00', min: '9:00 AM', max: '4:30 PM', default: '10:00 AM', :ignore_date => true, :required => true %>
<br><br>
<br><br>
<%= request.label :NumberOfBikesWanted, 'Number of bikes' %>
<%= request.select :number_of_bikes_wanted, %w(select_bike), :required => true %>
<br>
<%= request.submit 'Submit' %>
<%= request.submit 'Reset', :type => 'reset' %>
<% end %>
<br>
</div>
There are a two main problems with your code:
Controller
Use a different action to set the endpoint that you will call with ajax, so instead of this:
def new
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
#new_request = Request.new
end
Try this:
def bikes
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
def new
#new_request = Request.new
end
If you want to keep REST routes, then create a new controller and use the index action within that controller.
Form
This code:
var options = "";
<% #bikes.each do |bike| %>
options += "<option value='<%= bike.id %>'><%= bike.name %></option>"
<% end %>
$('#request_number_of_bikes_wanted').html(options);
doesn't belong here, it must be deleted from your file and instead put it on a new file called bikes.js.erb; also rename your form to _form.html.erb.
And update your ajax call to use your new route:
$.ajax({
type: "POST",
url: <%= bikes_path %>,
data: data
});
What you want to setup is a new endpoint but instead of returning html, it will return a js. But you must treat it as an independent action, just as any other action in rails. The only difference is how you call that action (ajax) and how you respond to it (js).
I need in my app to select from a list of provider a provider, and then, via ajax, I could see below a list of categories, that belongs to a specific provider. I have a form in activeadmin:
<%= semantic_form_for [:admin, #game], builder: ActiveAdmin::FormBuilder do |f| %>
<%= f.semantic_errors :state %>
<%= f.inputs do %>
<%= f.input :categorization_id, label: 'Provider', as: :select,
collection: Provider.all.map { |provider| ["#{provider.name}", provider.id] },
input_html: { class: (:provider_select), 'data-url': category_select_path(provider: 4) } %>
<%= f.input :categorization_id, label: 'Category',input_html: { class: ('category_dropdown') }, as: :select,
collection: Category.all.map { |category| ["#{category.name}", category.id]}%>
...
<% end %>
<%= f.actions %>
<% end %>
In activeadmin controller I have:
controller do
def ajax_call
#provider = Provider.find(params[:provider])
#categories = #provider.categories
respond_to do |format|
format.json { render json: #categories }
end
end
end
JS:
$(document).on('ready page:load', function () {
$('.select.input.optional').last().addClass('hidden_row');
$('#game_categorization_id').change(function () {
var id_value = this.value;
$('.hidden_row').removeClass('hidden_row');
$.ajax({
type: 'GET',
url: '/admin/games/category_select'
// data: id_value
})
});
});
And the routes: match '/admin/games/category_select' => 'admin/games#ajax_call', via: :get, as: 'category_select'
I don't have an idea, how can I pass providers id from collection into url. Currently, I have there category_select_path(provider: 4), but actually it has to be smth. like this - category_select_path(provider: provider.id) In the browser, in a Network tab of devtools I can see my category_select, but there is an error: Couldn't find Game with 'id'=category_select. I can't figure out from where does it come. Any suggestions? Thanks.
The problem was in routes: match '/admin/games/category_select' => 'admin/games#ajax_call', via: :get, as: 'category_select'. It is reserved by a show action of Activeadmin controller. So I changed my routes to: get '/admin/select_category' => 'admin/games#get_providers_categories', as: 'select_category', and added to ajax call data: {provider: provider}, so I could fire in params id of provider:
$.ajax({
type: 'GET',
url: '/admin/select_category',
data: {
provider: provider
},
success: (function (data) {
$('#select_category').children('option').remove();
$('#select_category').prepend('<option value=""></option>');
$.each(data.categories, function () {
$('#select_category').append('<option value="' + this.id + '">' + this.name + '</option>')
})
})
})
How can I use simple_form to filter a field, based on a previous fields value?
For instance, I have an Opportunities form, with two fields, Company and Contact.
Company Field:
<div class="form-group">
<%= f.association :company, collection: Company.all.order(:account), prompt: "", :label_method => :account, :value_method => :id %>
</div>
Contact Field:
<div class="form-group">
<%= f.association :contact, collection: Contact.all.order(:first_name), prompt: "", :label_method => lambda { |contact| "#{contact.first_name} #{contact.last_name}" }, :value_method => :id %>
</div>
Here is what I want to do: If I select a company called "Deviant" from the Company field above, I want the Contact field to only display those contacts associated with the company called "Deviant".
I am trying something like this, but can't get it to work:
<div class="form-group">
<%= f.association :contact, collection: Contact.where("company_id = ?", params[:id]), prompt: "", :label_method => lambda { |contact| "#{contact.first_name} #{contact.last_name}" }, :value_method => :id %>
</div>
I don't know how to reference the value in the Company field.
How can I do this?
Thanks.
Update
Anyone? Surely this must be possible. This is a key functionality in any form. I would hope I don't need jQuery or something.
I think the best approach is to use ajax requests to update your contacts collection dinamically whenever the company's selected value is changed.
First you'll need an action in your contacts controller:
app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def contacts_list
if params[:company_id]
#contacts = Contact.where(company_id: params[:company_id])
else
#contacts = Contact.all
end
respond_with(#contacts) do |format|
format.json { render :json => #contacts.to_json(:only => [:id, :first_name, :last_name]) }
end
end
end
Add this to your routes:
config/routes.rb
post 'contacts_list' => "contacts#contacts_list", as: :contacts_list
Then use the coffeescript code bellow to populate your contacts' collection:
app/assets/javasctipts/companies.js.coffee
$(document).ready ->
if $("#opportunity_company_id")
populate_contacts()
$("#opportunity_company_id").change ->
populate_contacts()
populate_contacts = ->
$contacts_select = $("select#opportunity_contact_id")
$contacts_select.attr "disabled", "disabled"
company_id = $("select#opportunity_company_id").val()
if company_id is ""
$contacts_select.html "<option value=\"\">(select the company first)</option>"
else
$contacts_select.html "<option value=\"\">(loading contacts...)</option>"
data = {company_id: company_id}
data[window._auth_token_name] = window._auth_token
$.ajax "/contacts_list",
type: "post"
dataType: "json"
data: data
success: (contacts) ->
_html = '<option value="">Select the contact:</option>'
_html += '<option value="'+contact.id+'">'+contact.first_name + ' ' + contact.last_name + '</option>' for contact in contacts
$contacts_select.html _html
$contacts_select.removeAttr "disabled"
error: ->
alert 'Error trying to load contacts.'
Finally, inside your html's head tag:
<% if protect_against_forgery? %>
<script>
window._auth_token_name = "<%= request_forgery_protection_token %>";
window._auth_token = "<%= form_authenticity_token %>";
</script>
<% end %>
Hope it helps...
update:
Add the following line to your ApplicationController (app/controllers/application_controller.rb):
respond_to :html, :xml, :json, :js