Rails select options dynamically changing based on another select (without ajax) - ruby-on-rails

I have a form where a user selects a school, and then a qualification that the school provides:
<div class="row">
<div class="col-md-6">
<%= f.association :education_organization,
collection: EducationOrganization.active,
include_blank: true
%>
</div>
<div class="col-md-6">
<%= f.association :training_qualification,
collection: TrainingQualification.active,
include_blank: true
%>
</div>
</div>
I would like to dynamically scope the qualifications select to only show the qualifications available to the school that is selected.
I don't want to do an ajax call, I'd rather send the whole list at the start and have the client switch the select options offline.
What's the best way to do this?

I found a way, by initially loading all options, then storing the html options and filtering that list by the id of the association as a data attribute.
View:
<%= f.association :education_organization,
collection: EducationOrganization.active,
include_blank: true
%>
<%= f.association :training_qualification,
collection: TrainingQualification.active.map { |tq| [tq.name, tq.id, {data: {org: tq.education_organization.id}}] },
include_blank: true
%>
JS:
$(function(){
// this is a hack that lets the JS be included anywhere, but only run on pages with the relevant form
var qualifications_select = $('#user_training_qualification_id');
if(qualifications_select) {
// this stores all select options
var qualification_options = qualifications_select.html();
function updateQualSelect(qualifications_select) {
var selected_org_id = $('#user_education_organization_id :selected').val();
if(selected_org_id != '') {
var filtered_options = $(qualification_options).filter(`[data-org=${selected_org_id}]`);
qualifications_select.html(filtered_options);
} else {
qualifications_select.empty();
}
}
// this line filters the select when the other select changes
$('#user_education_organization_id').change(function() {
updateQualSelect(qualifications_select);
});
// this line filters the select on page load
updateQualSelect(qualifications_select);
}
})

Related

If Else in HAML to check the value of a "select" box

= form_tag questions_path, :method=>:post do
= label :question, :type, 'Type: '
= select :question, :type, %w(Text Picture Audio Video), :id=> :question_type_combo
**- if :question_type_combo.selected != 'Text'**
= label :question,:url, 'URL: '
= text_field :question,:url, :id=> :question_url_text
= submit_tag 'Add Question',:id=>:add_question_button
Is something of this sort possible in HAML? I wish to render the textfield only for certain options if selected in the SELECT BOX above.
Yes and no. You can write a conditional based on the values of the record that you bind to the form:
= form_for #question do |f|
= f.label :type
= f.select, :type, %w(Text Picture Audio Video), id: 'question_type_combo'
- unless f.object.question_type_combo === 'Text'
= f.label :url
= text_field :url, id: 'question_url_text'
But this would only change the visibility after the user submits the form and not be very useful.
Instead you can just use jQuery to create an event handler for the ´change´ event.
$(document).on('change','#question_type_combo', function(){
var type = $(this).first(':selected').val();
var $other_input = $('#other_input');
if (type == 'Text') {
$other_input.hide();
} else {
$other_input.show();
}
});
// sets the initial state
// if you are using turbolinks
$(document).on('page:load', function(){
$('#question_type_combo').trigger('change');
});
// if you are not using turbolinks
$(function(){
$('#question_type_combo').trigger('change');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
<div class="field">
<label>Type</label>
<select name="question[question_type_combo]" id="question_type_combo">
<option>Text</option>
<option>Something else</option>
</select>
</div>
<div class="field" id="other_input">
<label>URL</label>
<input type="text" name="question[url]">
</div>
</form>
- if :question_type_combo.selected != 'Text' This is not possible in the haml view, If you want to do something based on selected option you have to use js.
Or you may set the selected option using similar code if you have controller objects:
= select_tag("fee_discount", options_for_select(Fee.discounts.keys.map {|k| [k.titleize, k]}, selected: "#{"rewards" if #vendor.present? && #vendor.approved?}"), include_blank: true)
Or
You may keep the label and text_field inside a div with hide class.
And then using javascript you may hide unhide the div.

Select2 rails form repopulation

I created a rails (v5) form with multiple select and collection_select elements.
Then I use Select2-rails (v4.0.3) to allow a nice selection looking like tags.
The search-options are pulled by ajax.
It works fine until one presses the submit-button with missing required fields.
Valid field-content has now been deleted from the field.
Let me give some example-code:
controller:
...
def form
if params[:form_request].nil?
#form_request = FormRequest.new
else
#form_request = FormRequest.new(params[:form_request])
end
end
def request_form
#form_request = FormRequest.new(params[:form_request])
if #form_request.valid?
render :summary
else
render :form
end
end
...
form:
...
<%= bootstrap_form_for(#form_request, url: '/form/request_form') do |f| %>
<%= f.select :field, [], {label: 'Field label'} %>
<%= f.submit "Submit form" %>
<% end %>
:field is for sure a writable field in the model (and data is set fine)
coffee-script:
Query ->
$("#form_request_from").select2({
ajax: {
url: func =(params) ->
filter = params.term
return "/data.json?filter=" + filter;
,
dataType: 'json',
processResults: processData
},
theme: 'bootstrap',
placeholder: 'Enter data here'
});
processData = (data) ->
mapdata = $.map(data, func =(obj) ->
obj.id = obj.id;
obj.text = obj.name;
return obj;
);
return { results: mapdata };
I am thinking of a lot of possibilities, but at the end I am not sure where the field-data comes from. It is inside the object, but it isn't written to the resulting HTML in any way.
And even if the id would be written as a selected option,
the select2 script would need to know how to transform that into the string to show the real data.
Any idea how to achieve that the data is still written into a field after a failing validation?
After trying out some things I found out how to do it.
At first I just changed the empty array to be the :field variable,too.
This doesn't work too well because it only remembers the ID of the value that has been entered before and like this the SELECT2-script could not find the value to that key and nothing is shown.
Then I created a new variable inside the controller in which I place the array with name and id:
field_object = ObjectsModel.find(#form_request.field.to_i)
#form_field = []
if !field_object.nil?
#form_field = [[field_object.name, field_object.id]]
end
And in the view I now use this field to show the available options:
<%= bootstrap_form_for(#form_request, url: '/form/request_form') do |f| %>
<%= f.select :field, #form_field, {label: 'Field label'} %>
<%= f.submit "Submit form" %>
<% end %>
This works perfectly fine for me without the need to touch the SELECT2-script.
The possible values are still fetched by AJAX but already filled out fields will persist upon redirect to another action.

Select option required Ruby Rails

I have a mix of ruby rails code
I have a form with a selection option that i want to be a required, and i want to validate. If user do not select anything i want to validade with a error message.
However, I try to copy past code from internet, I'm new at ruby rails and I still not have a error message.
I also check that i use 'required' , or if i use 'validates_presence_of' doesn't make difference because it's a submit form (i think)
test_filteR_form.rb
class TestFilterForm < ApplicationForm
attribute :model, String
validates_presence_of :model
end
.html.erb
<%= f.input :fill_form_error_message,:as => :hidden, :input_html => { :value =>I18n.t('test.fill_form_error') } %>
<%= f.input :model, label: I18n.t('test.filters.model'), autofocus: true, input_html: {class: 'input-xlarge chosen-select' }, collection: TestType.options_for_select, include_blank: true %>
"/>
controller
def paginate
#test_form = TestForm.new(params)
unless #test_form.valid?
#model = params[:test_filter_form][:model]
#h_model = #model.pluralize + 'H'
#history, _query, #test_fields = TestQueryService.search!(params)
session[:test_query] = _query
session[:test_klass] = #model
else
format.json { render :json => { :error => #test_form.errors.full_messages }, :status => 422 }
end
js.coffee
$contentDiv.on 'ajax:error', 'form[data-value]', (event, xhr, status, error) ->
data = $.parseJSON(xhr.responseText)
$result = $(#).parents('tr').find('[data-result]')
controller.resultUpdateError xhr.status, $result.data('result'), data, $(#)
# Hide row loading spinner
$(#).parents('tr').find('span[role="result_update_spinner"]').hide()
# Hide saved form
$(#).parents('tr').find('.saved_form').hide()
resultUpdated: (result, data, $form) ->
if data.flash != undefined
# Sets a sucess message on page top
flash data.flash.type, data.flash.message
# Sets a success message on row
$fieldForm = $form.parents('tr').find(".messages")
$fieldForm.find('.controls').empty()
$fieldForm.find('.control-group .controls').css('color', 'green').append #_inlineMessage("Gravado com sucesso")
# Hide success message after some time
setTimeout ((self) ->
->
$fieldForm.find('.control-group .controls').empty()
return
)(this), 4000
Since you are dynamically created the selection box then there must be a default value selected which is not nil so there is no change seen you can manually create selection like this:
<div class="form-group">
<%= f.label :select_user_country %><br/>
<select class="form-control select2" name="user[country_id]">
<option value="" selected disabled>Select a Country</option>
<%#countries.each do |country|%>
<option value="<%=country.id%>"><%=country.name%></option>
<%end%>
</select>
</div>

Add character counter in simple_form in Ruby on Rails

I use the simple_form gem and I want a simple character counter on a text field. I was told, this might work:
add this to the form:
<%= f.input :body, id: "body-field" %>
<span id="body-count">0 characters</span>
and javascript:
$("#body-field").on("keyup", function(){
length = $(this).val().length;
$("#body-count").html(length);
});
I got this information from here (Attention: It is full of advertisement): http://www.sohua.xyz/questions-full/4320915/how-do-i-implement-a-basic-character-counter-in-a-simple-form
I did this, but nothing happens. Here is my actual code chapters/new.html.erb:
<%= simple_form_for([#book, #book.chapters.build]) do |f| %>
<%= f.input :chaptertitle %>
Mininmum amount of characters: <%= #book.min_length %> Maximum amount of characters: <%= #book.max_length %>
<%= f.input :chaptercontent, id: "body-field" %>
<span id="body-count">0 characters</span>
<%= f.input :author %>
<%= f.button :submit %>
<% end %>
<script>
$("#body-field").on("keyup", function(){
length = $(this).val().length;
$("#body-count").html(length);
});
</script>
Can you give me any advice, how to get it work?
You need to wrap your code in jquery document ready function:
$(function() {
$("#body-field").on("keyup", function(){
var length = $(this).val().length;
$("#body-count").html(length);
});
});
Why don't you use an existing library instead?
https://github.com/dtisgodsson/jquery-character-counter
You might want to use either js or coffee-script, I am providing a coffee script example below:
Add this piece of code to your chapters.coffee file:
ready = ->
totalChars = 100
#Total characters allowed
countTextBox = $('#body-field')
# Remaining chars count will be displayed here
charsCountEl = $('#countchars')
#initial value of countchars element
charsCountEl.text totalChars
#user releases a key on the keyboard
countTextBox.keyup ->
#get chars count in Text field
thisChars = #value.replace(/{.*}/g, '').length
if thisChars > totalChars
# total extra chars to delete
CharsToDel = thisChars - totalChars
#remove excess chars from text field
#value = #value.substring(0, #value.length - CharsToDel)
else
#count remaining chars
charsCountEl.text totalChars - thisChars
return
return
$(document).ready ready
$(document).on 'page:load', ready
# Loads javascript while loading page
Add this line to your form right below to the Text input field.
var ready;
var charsCountEl, countTextBox, totalChars;
totalChars = 100;
countTextBox = $('#body-field');
charsCountEl = $('#countchars');
charsCountEl.text(totalChars);
countTextBox.keyup(function() {
var CharsToDel, thisChars;
thisChars = this.value.replace(/{.*}/g, '').length;
if (thisChars > totalChars) {
CharsToDel = thisChars - totalChars;
this.value = this.value.substring(0, this.value.length - CharsToDel);
} else {
charsCountEl.text(totalChars - thisChars);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<input type="text" id="body-field" />
<span id="countchars"></span>
If your file under javascript/ folder doesn't have extestion .coffee the please rename it to chapters.coffee if it does then thats it.
PS: here is the javascript version of the same http://pastebin.com/LZb1DAC4.
Reference: https://stackoverflow.com/a/24629105/2545197
This solution won't be as good as Abhinay's solution, but it works for me:
(Please note, I'm an amateur, this code may be horrendous)
Javascript code:
<script>
$(document).ready(function(){
var choosingbar = function( event ){
$(event.target).parents(".counting").children(".count").text($(event.target).val().length);
};
$(".counting textarea").keyup(choosingbar);
});
</script>
new.html.erb code:
<%= simple_form_for([#book, #book.chapters.build]) do |f| %>
<%= f.input :chaptertitle %>
Mininmum amount of characters: <%= #book.min_length %> Maximum amount of characters: <%= #book.max_length %>
<div class="counting" ><%= f.input :chaptercontent %>Characters: <span class="count"></span></div><br>
<%= f.input :author %>
<%= f.button :submit %>
<% end %>

How to use AngularJS with Rails Form?

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!

Resources