Jquery Tokeninput & Dynamic Nested Forms - javascript

I am using the setup of the R.B. railcast - #197 Nested Model Form Part 2 to dynamically add fields to the form but i am having issues getting the Tokeninput fields to work.
Form:
<%= form_for(current_user, :url => user_products_path) do |f| %>
<%= f.error_messages %>
<%= f.fields_for :user_products do |builder| %>
<%= render "user_product_fields", :f => builder %>
<% end %>
<%= link_to_add_fields "Add a UserProduct", f, :user_products %>
<%= f.submit "Create" %>
<% end %>
This is my user_product_fields partial, the token text_fields are what I'm having the issue with:
<div class="fields">
<%= f.label :product_token, "Product" %>
<%= f.text_field :product_token, :id => 'product_token' %>
<%= f.label :address_token, "Address" %>
<%= f.text_field :address_token, :id => 'address_token' %>
<%= f.label :price %>
<%= f.text_field :price %>
<%= link_to_remove_fields "remove", f %>
</div>
Jquery Tokeninput functions inside of my application.js:
$(function() {
$("#product_token").tokenInput("/products.json", {
prePopulate: $("#product_token").data("pre"),
tokenLimit: 1
});
});
$(function() {
$("#address_token").tokenInput("/business_addresses.json", {
prePopulate: $("#address_token").data("pre"),
tokenLimit: 1
});
});
What the nested form does in the function is this:
function add_fields(link, association, content) {
var new_id = new Date().getTime();
var regexp = new RegExp("new_" + association, "g")
$(link).parent().before(content.replace(regexp, new_id));
}
function remove_fields(link) {
$(link).prev("input[type=hidden]").val("1");
$(link).closest(".fields").hide();
}
This line here:
var new_id = new Date().getTime();
Makes the tokeninput fields dynamic, this is what i pulled up from the HTML, notice the changing long numbers in the fields. This is because of the line above.
<label for="user_user_products_attributes_1313593151076_product_token">Product</label>
<label for="user_user_products_attributes_1313593146876_product_token">Product</label>
<label for="user_user_products_attributes_1313593146180_product_token">Product</label>
How can i get my token fields to work when the fields keep changing up?
Thank you.
EDIT: New working code.
function add_fields(link, association, content) {
var new_id = new Date().getTime();
var regexp = new RegExp("new_" + association, "g")
$(content.replace(regexp, new_id)).insertBefore($(link).parent()).trigger("nestedForm:added");
}
$('div.fields').live("nestedForm:added", function() {
$("#product_token", $(this)).tokenInput("/products.json", {
prePopulate: $("#product_token", $(this)).data("pre"),
tokenLimit: 1
});
});
When trying to data-pre with TokenInput:
def new
#user_product = current_user.user_products.build
# This line below is for TokenInput to work, This allowed me to use #products.map on the form.
#products = []
end
def edit
#user_product = UserProduct.find(params[:id])
# This line below had to find the Product associated with the UserProduct
#products = [#user_product.product]
end

You can user jQuery's insertBefore instead of before as that will return the inserted element. It will help you to trigger some event. You can have a listener on this event, in which you can have you token-input code. You should also use class instead of id, as many elements can have same class, but no two elements should have same id.
$(content.replace(regexp, new_id)).insertBefore($(link).parent()).trigger("nestedForm:added");
$('div.fields').live("nestedForm:added", function() {
$(".product_token", $(this)).tokenInput("/products.json", {
prePopulate: $(".product_token", $(this)).data("pre"),
tokenLimit: 1
});
});
This is just an idea, code is not tested.

Related

Is there a way to regenerate link_to_add_association (specifically data-association-insertion-template)?

Built a very basic rails app to manipulate nested attributes using cocoon and the add and remove links work great. However, it wasn't too long until I wanted to alter the underlying content of what was inserted, say in response to another field changing the list of option values in an included select tag. It appears that the contents to be added are contained in an 'a' tag data element (data-association-insertion-template). I can quite easily change the select options for all included lines via jQuery but changing the behavior of the link_to_add_association is beyond me.
Here are snippets of my example:
_form.html.erb
<div>
<strong>Entries:</strong>
<div id="entries" style="border: thin solid">
<%= f.fields_for :entries do |oi| %>
<%= render "entry_fields", f: oi %>
<% end %>
<div class="links">
<%= link_to_add_association 'Add Entry', f, :entries, {id: 'cocoon-add-entry'} %>
</div>
</div>
</div>
_entry_fields.html.erb
<div class="nested-fields">
<%= f.label :item_id %>
<%= f.select :item_id, #items.collect {|i| [i.style, i.id]}, {include_blank: true}, {selected: :item_id, multiple: false} %>
<%= f.label :decoration_id, 'Decoration' %>
<%= f.select :decoration_id, #decorations.collect { |d| [ d.name, d.id ] }, {include_blank: true}, {selected: :decoration_id, multiple: false, class: 'decoration'} %>
<%= f.label :color %>
<%= f.text_field :color %>
<%= f.label :size_id %>
<%= f.select :size_id, #sizes.collect { |s| [ s.name, s.id ] }, {include_blank: true}, {selected: :size_id, multiple: false} %>
<%= f.label :number %>
<%= f.number_field :number, value: 1, min: 1 %>
<%= f.check_box :_destroy, hidden: true %>
<%= link_to_remove_association "Remove Entry", f %>
</div>
orders.coffee
ready = ->
$('.customer').change ->
$.ajax
url: '/orders/change_customer'
data: { customer_id : #value }
$(document).ready(ready)
$(document).on('turbolinks:load', ready)
order_controller.rb
def change_customer
#decorations = Decoration.joins(:logo).where('logos.customer_id = ?', params[:customer_id])
respond_to do |format|
format.js
end
end
change_customer.js.erb
// update all existing entry decorations with new customer driven options
<% new_decor = options_from_collection_for_select(#decorations, :id, :name) %>
var new_decor_options = "<option value='' selected='selected'></option>" + "<%=j new_decor %>";
$('.decoration').html(new_decor_options);
// now need to change $('#cocoon-add-entry').attr('data-association-insertion-template, ???);
// or regenerate link entirely - but don't have required data to do so here (form builder from original)
I have tried to manipulate the template data string directly via js str.replace but that is one ugly regular expression because of the unescapeHTML and htmlsafe operations done to make it an attribute in the first place. And, that approach doesn't smell good to me. I have been slowly working through the cocoon view_helpers and javascript but nothing seems to fit or I don't seem to have the right methods/data values to build a replacement link. Suggestions?
BTW, kudos for cocoon gem.
After a lot of gnashing of teeth, I have cobbled together a potential solution. I haven't decided if I will let this go into production yet because of the limitations but thanks to combining several different SO questions and answers, the following works:
change_customer.js.erb
// update all existing entry decorations with new customer driven options
<% new_decor = options_from_collection_for_select(#decorations, :id, :name) %>
var new_decor_options = "<option value='' selected='selected'></option>" + "<%=j new_decor %>";
$('.decoration').html(new_decor_options);
// update the Add Entry link to capture new decorations set.
// Note use of ugly hack to recreate 'similar' form.
// Also note that this will only work for new order; will have to revise for edit.
'<%= form_for(Order.new) do |ff| %>'
$('#cocoon-add-entry').replaceWith("<%=j render partial: 'add_entry_link', locals: {f: ff} %>");
'<% end %>'
_add_entry_link.html.erb
<%= link_to_add_association 'Add Entry', f, :entries, {id: 'cocoon-add-entry', data: {'association-insertion-method' => 'after'}} %>

Access form_group div via id

I want to perform a check to provide instant validation feedback on the input of a field in my bootstrap form for my Profiles model using java/coffee-script. My current solution is working but feels rather clunky and I would like to improve it.
Here are some code examples of what I'm doing.
app/views/profiles/_form:
<%= bootstrap_form_for(#profile, layout: :horizontal, html: {id: "profile-form"} ) do |f| %>
<%= f.text_field :name %>
<%= f.text_field :number, id: "number-field" %> //Field to check
<%= form_group do %>
<%= f.primary %>
<% end %>
<% end %>
app/assets/javascript/profile_number_control.coffee:
$(document).on 'ready page:load', ->
$field = $("#number-field")
$group = document.getElementById("profile-form").childNodes[5]
numberPattern = "^(19|20)[0-9]{6}-[0-9]{4}$"
$helpBlock = $group.getElementsByClassName("help-block")[0]
$field.keyup ->
//This works fine, just wanted to show what is going on
if inputIsValid()
set $group class to " has-success"
set $helpBlock message to successful
else
set $group class to " has-error"
set $helpBlock message to corresponding error message
Here is the problem. I want this script to work for both profile#new and profile#edit, but since the array of elements given by document.getElementById("profile-form").childNodes differs in length depending of the current view I can not use the same index (5 in the example) for both #new and #update.
Does anyone know how to set an id to that specific form_group in my view? Or do I have to actually write out the whole html code to be able to place an id there properly?
This might help !
<%= bootstrap_form_for(#profile, layout: :horizontal, html: {id: "profile-form"} ) do |f| %>
<%= f.text_field :name %>
<%= f.text_field :number, :input_html => { id: "number-field" } %> //Field to check
<%= form_group do %>
<%= f.primary %>
<% end %>
<% end %>

Live search with AJAX in rails 4

I'm fairly new to programming with ruby . I'm trying to create a Livesearch with AJAX, but is giving me headaches. The basic idea is that when a user creates a node, can be stored in the same: information of the user who created it , the parent node , etc. For this I did a live search where the user enters the ID of the parent node and the user data that corresponds to that node automatically appear . Then, in the same view, I want to create the node. So far I have the following code, which I'm not doing work.
I want something like this:
(source: subeimagenes.com)
Problems:
AJAX not working , I'm sure I 'm forgetting something important
Do not know how to pass the value " parent_id " (u.id in _users.html.erb, result of " live search ") to create the node.
nodes_controller.rb
def new
#node = Node.new
end
def search
#node = Node.new
#parent = Node.search(params[:search]).where(:ocuped => true)
if not #users.nil?
if #users.count == 1
#node_incomplete = #users.nodes.where(" sons < ? AND ocuped = ?",2,true).first
else
#node_incomplete = #users.first.nodes.where(" sons < ? AND ocuped = ?",2,true).first
end
#son_of_incompleted_node = #node_incomplete.children
end
respond_to do |format|
format.html
format.js
end
end
search.html.erb
<%= form_for #node, :id => "users_search" do |f| %>
<%= f.text_field :search, :value => params[:search], :autocomplete => 'off' %>
<div id='users'>
<%= render 'users' %>
</div>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.hidden_field :ocuped, :value => true %>
<%= f.text_field :custom_node_name %>
<%= f.check_box :terms_of_service,{}, true,false %>
<%= f.submit "Create", class: "button postfix" %>
<% end %>
_users.html.erb
<% if not #parent.nil? %>
<% #parent.each do |u| %>
<ul class="inline-list">
<li class="img-simple" style = "text-align: center; background-image: url('<%= u.user.profile_img %>')"></li>
<li style="display:table-cell; vertical-align: middle;"><h6><%= u.user.name %></h6><p><%= u.user.email %></p></li>
</ul>
<% end %>
node.rb
def self.search(search)
self.where('id = ? ', search.to_i)
end
application.js
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require foundation
//= require_tree .
$(function() {
$("#users_search input").keyup(function() {
$.get($("#users_search").attr("action"), $("#users_search").serialize(), null, "script");
return false;
});
});
nodes.js.cofee
jQuery ->
# Ajax search on keyup
$('#users_search input').keyup( ->
$.get($("#users_search").attr("action"), $("#users_search").serialize(), null, 'script')
false
)
views/nodes/search.js.erb
$('#users').html('<%= escape_javascript(render("users")) %>');

How to Change Date Format According to User Selection?

How can I change the date :order according to the User's "f.select :categories" choice?
For example when a User goes to the Quantified _form he can select between "Monthly Average" or "One-Time Instance";
If he selects "Monthly Average" I want the _results_fields.html.erb to provide the date selection as ":order => [:month, :year]" if he selects "One-Time Instance" I want the _results_fields.html.erb to provide the date selection as ":order => [:month, :day, :year]
Ideally this would be done dynamically via javascript, but I would be happy to accept an answer that does it via the view.
_results_fields.html.erb
<div class="nested-fields">
<div class="form-group">
<%= f.text_field :result_value, class: 'form-control', placeholder: 'Enter Result' %>
<br/>
<%= f.date_select :date_value, :order => [:month, :year], class: 'date-select' %>
<%= link_to_remove_association "Remove Result", f %>
</div>
</div>
_form.html.erb
<%= form_for #quantified do |f| %>
<div class="america">
<%= f.select :categories, Quantified::CATEGORIES %>
<br/>
<br/>
<div class="form-group">
<%= f.text_field :name, class: 'form-control', placeholder: 'Enter Name' %>
</div>
<div class="form-group">
<%= f.text_field :metric, class: 'form-control', placeholder: 'Enter Metric' %>
</div>
<div id="results">
<%= f.fields_for :results do |result| %>
<%= render 'result_fields', :f => result %>
<% end %>
</div>
<div class="links">
<%= link_to_add_association 'add result', f, :results %>
</div>
<%= f.submit %>
</div>
<% end %>
quantified.rb
class Quantified < ActiveRecord::Base
belongs_to :user
scope :averaged, -> { where(categories: 'Monthly Average') }
scope :instance, -> { where(categories: 'One-Time Instance') }
has_many :results
accepts_nested_attributes_for :results, :reject_if => :all_blank, :allow_destroy => true
CATEGORIES = ['Monthly Average', 'One-Time Instance']
end
Github: https://github.com/RallyWithGalli/ruletoday
Thank you for your time.
The answer here was to use some jquery provided by the gem that was being used to add the date selects, as well as some jquery of our own
First things first, we rig up the checkboxes with a click() event, that hides anything with the class 'day' if instance is checked, and shows it if not.
$('.date-toggle').click(function(){
if( $(this).attr('id') == 'instance') {
$('.day').show();
} else {
$('.day').hide();
}
});
The trick here is to use the rails date select option :with_css_classes on the date select, so that it looks like this:
<%= f.date_select :date_value, :order => [:month, :day, :year], :with_css_classes => true, class: 'date-select' %>
This causes each included field to get a class corresponding to its name, hence our reference to '.day' up there.
The next important part, however, because he is using a gem called cocoon to add new elements that included date fields, we had to use some of their jquery event bindings to make sure that the new ones got the right action applied to them.
$('#container').on('cocoon:after-insert', function(e, insertedItem) {
if($('#instance').is(':checked')) {
$('.day').show();
} else {
$('.day').hide();
}
});
Technically, the $('.day').show() is not necessary, because it defaults to being shown, but this, to me, gets across the intent a little clearer.
I think my solution is a little bit messy, but hopefully it gives you an indication of my train of thought and give you something to build upon.
I have changed your categories to work with a radio button group because the select field is a bit weird for 2 elements (subjective I know), to change it back to a select form,simply keep your code as is, but you will need to check the value of the select dropdown using jQuery .change() or some other vanilla Javascript way...
You haven't provided any controller names, so I didn't know how to name my JS file, but hopefully you know how to integrate.
IMO, I'm not sure why this needs AJAX, so maybe you can explain to me where that comes in.
_form.html.erb
<%= Quantified::CATEGORIES.each do |c| %>
<%= f.radio_button(:category, c) %>
<%= label(c, c) %>
<% end %>
_results_fields.html.erb
<%= f.date_select :date_value, :order => [:month, :year], class: 'date-select-my' %>
<%= f.date_select :date_value, :order => [:month, :year], class: 'date-select-mdy' %>
vanilla.js
$(document).ready(function(){
$('.date-select-mdy').hide();
$('.date-select-mdy').attr("disabled", true);
$('input[name$=\category\']').click(function() {
var target_value;
target_value = $(this).val;
if (target_value === 'Monthly Average') {
$('.date-select-my').show();
$('.date-select-mdy').hide();
$('.date-select-my').attr("disabled", false);
return $('.date-select-mdy').attr("disabled", true);
} else {
$('.date-select-my').hide();
$('.date-select-mdy').show();
$('.date-select-my').attr("disabled", true);
return $('.date-select-mdy').attr("disabled", false);
}
});
});
a_little_bit_of_coffee_because_i_dislike_js.coffee
$->
$('.date-select-mdy').hide()
$('.date-select-mdy').attr("disabled",true)
$('input[name$=\category\']').click ->
target_value = $(this).val
if target_value == 'Monthly Average'
$('.date-select-my').show()
$('.date-select-mdy').hide()
$('.date-select-my').attr("disabled",false)
$('.date-select-mdy').attr("disabled",true)
else
$('.date-select-my').hide()
$('.date-select-mdy').show()
$('.date-select-my').attr("disabled",true)
$('.date-select-mdy').attr("disabled",false)
return

Part of form not showing issue

I have a rails 4 app where I'm following basically this railscast:
_form.html.erb:
<%= form_for #store, do |f| %>
<%= f.fields_for :products do |builder| %>
<%= render "product_fields", f: builder %>
<% end %>
<%= link_to_add_fields "Add Product", f, :products %>
<% end %>
_product_fields.html.erb
<%= f.select :the_product_type, %w[Shelves, Tools, Wires]%>
<div>
<%= f.fields_for :product_fields do |builder| %>
<%= builder.text_area :name_of_product_field %>
<% end %>
</div>
My JS looks like:
$('form').on('click', '.add_fields', function(e) {
var regexp, time;
time = new Date().getTime();
regexp = new RegExp($(this).data('id'), 'g');
$(this).before($(this).data('fields').replace(regexp, time));
return e.preventDefault();
});
My issue is that when I click the Add Product button, I can only see a select. I can't see the name_of_product_field textarea. But I can't figure out why I can see the select if I can't see the textarea?
product_fields is a nested attribute which you have not build anywhere in your code which is why you are not seeing it.
Assuming that a product has_many product_fields, you can resolve this issue in two ways, choose one that suits you:
1. Build it at Controller level
Build the product_fields in the Controller#action which is rendering the problematic view:
def action_name
#store = Store.new
product = #store.products.build
product.product_fields.build
end
2.Build it at View level
Update the fields_for in _product_fields.html.erb as below:
<%= f.fields_for :product_fields, f.object.product_fields.build do |builder| %>

Categories

Resources