I have a Rails 3.2.18 app in which I'm doing a simple create/update action on a model.
There's a field called sms which I used ActionMailer to send a message to a person's phone in the form of 2812222222#vtext.com. Currently I manually type this in which is fine, but I want other's to be able to enter a phone number, select a carrier and have these two items merge into the proper format in the sms field again being 2812222222#vtext.com. Makes it a lot more user-friendly than having to know the full sms-to-email address by heart.
I assume I can do most of this via JS/JQuery, but I'm not sure on how to get started.
I'd like them to be able to enter the phone number in a 281-555-4444 format and have the carrier select as a drop-down of Tmobile, Verizon, etc and substitute #tmomail.net, #vtext.com, etc, strip (regex) the phone number and merge it with the carrier (#vtext.com) into the sms field.
If anyone can point me in the right direct, it would be appreciated.
Update:
I figured I could use a helper method to define the carriers:
def phone_carriers
{
"All Tell" => "#message.alltel.com",
"AT&T" => "#txt.att.net",
"Boost" => "#myboostmobile.com",
"Cellular South" => "#csouth1.com",
"Centennial Wireless" => "#cwemail.com",
"Cincinnati Bell" => "#gocbw.com",
"Cricket Wireless" => "#sms.mycricket.com",
"Metro PCS" => "#mymetropcs.com",
"Powertel" => "#ptel.net",
"Qwest" => "#qwestmp.com",
"Rogers" => "#pcs.rogers.com",
"Sprint" => "#messaging.sprintpcs.com",
"Suncom" => "#tms.suncom.com",
"T-Mobile" => "#tmomail.net",
"Telus" => "#msg.telus.com",
"U.S. Cellular" => "#email.uscc.net",
"Verizon" => "#vtext.com",
"Virgin Mobile USA" => "#vmobl.com"
}
end
Add attr_accessor :carrier to the Person model
Then in the form do something like this:
<%= f.input :phone, placeholder: "555-555-5555", required: true, input_html: {required: true} %>
<%= f.select :carrier, phone_carriers.map{ |k, v| [k, v] }, {include_blank: true}, {placeholder: "Select your phone carrier", class: "select2"} %>
But I'm still having problems figuring out how to strip the phone number to 5555555555 and merging the :phone field with the :carrier object into the :sms field automatically (preferably client side)
After doing a lot of research and breaking things, I've come up with the following to make this work. The model and form only show what changes I've made, not the full form or model.
Form
<%= f.label 'Cell_Phone'%>
<%= f.text_field :phone, placeholder: "xxx-xxx-xxxx", required: true %>
<%= f.label :carrier, "SMS Notifications", class: "control-label" %>
<%= f.select :carrier, phone_carriers.map{ |k, v| [k, v] }, {include_blank: true}, {placeholder: "Select your phone carrier", class: "select2"} %>
<%= f.hidden_field :sms %>
Model
attr_accessible :carrier
attr_accessor :carrier
CoffeeScript
$ ->
$("#person_carrier").on "change", update_sms_address
$("#person_phone").on "keyup", update_sms_address
update_sms_address
update_sms_address = ->
digits = $("#person_phone").val().match(/\d+/g)
phone = digits.join("") if digits
carrier = $("#person_carrier").val()
if phone? && phone.length == 10 && carrier
sms = phone + carrier
$("#person_sms").val(sms)
$("#person-sms-number").html(sms)
else if !carrier
$("#person_sms").val("")
$("#person-sms-number").html("Choose a carrier to receive SMS notifications")
else
$("#person_sms").val("")
$("#person-sms-number").html("Invalid phone number")
This seems to work when entering a phone number in the format of 281-444-4444 as it strips the number down and joins it with the value from the carrier array and sets the hidden :sms field appropriately.
The only problem I have now is since I'm using a virtual attribute for the carrier, if I go to edit the person again it doesn't retain the carrier select and leaves it blank. If I submit the form without changing any info it will retain the :sms field, but it could be confusing since the :carrier is not an actual field that is remembered by the database but instead a virtual attribute.
Is there a better way to go about doing this?
If you used the standard javascript string library, wouldn't something like
function prepare(str){
str2 = str.replace("(","").replace(")","").replace("-","")
carrier = getCarrier //assuming this implemented by you somewhere
return (st2.concat(carrier))
}
I would start by creating a carriers table:
rails generate model Carrier name:string email_domain:string
And referencing it in the Person model by adding the following to your migration:
change_table :people do |t|
t.references :carrier
end
add_index :people, :carrier_id
And the following to your People model:
belongs_to :carrier
validates_presence_of :carrier
def sms
carrier ? "#{phone}##{carrier.email_domain}"
end
def phone_with_dashes
"#{phone[0..2]}-#{phone[3..5]}-#{phone[6..9]}"
end
def phone_with_dashes=(_phone_with_dashes)
self.phone = _phone_with_dashes.gsub(/-/ ,'')
end
Now, your view (I'll leave the formatting to you):
Phone: <%= f.phone_field :phone_with_dashes %>
Carrier: <%= f.collection_select :carrier_id, Carrier.all, :id, :name %>
<div id='person-sms-number'>
<%= f.object.sms %>
</div>
Lastly, the Coffeescript: The only thing that has to change here is you no longer need the '#person_sms' input since you are no longer persisting the value directly--you are generating it dynamically from other fields.
Related
I'm following this railscast on nested forms to create a similar application.
In Ryan's application he has a structure like:
Surveys ---> 2. Questions ---> 3. Answers
So a user can create a survey and add/delete questions, with corresponding answers that they can add/delete.
I'm trying to do it a little differently. I have:
Surveys ---> 2. Questions ---> 3. Answers Or Comment
For my application, after a user has created a question, the user can create an answer (just like Ryan's app). But there is a select box next to each answer, with the default being "Answer" and the other being "Comment". I'm trying to get basically the same functionality of Ryan's app, but with two tables: comments table and answers table.
My code mimics the railscast, except for my answer_fields partial looks like:
<fieldset>
<%= f.label :content, "Answer" %>
<%= f.select :switch_answer_table options_for_select(["Answer", "Comment"]), class: "change-type" %>
<%= f.text_field :content %>
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "remove" %>
</fieldset>
and I have a comment_fields partial that looks like:
<fieldset>
<%= f.label :content, "Comment" %>
<%= f.select :switch_answer_table options_for_select(["Comment", "Answer"]), class: "change-type" %>
<%= f.text_field :comment_content %>
... (leaving out some stuff) ...
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "remove" %>
</fieldset>
My question.rb model looks like:
class Question < ActiveRecord::Base
belongs_to :survey
has_many :answers
has_many :comments
accepts_nested_attributes_for :answers, allow_destroy: true
accepts_nested_attributes_for :comments, allow_destroy: true
end
I've been working on this, and I'm stuck. My jQuery only looks like this so far:
jQuery ->
$('form').on 'click', '.remove_fields', (event) ->
$(this).prev('input[type=hidden]').val('1')
$(this).closest('fieldset').hide()
event.preventDefault()
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
$('form').on 'change', '.change-type', (event) ->
# Confused on how to change partials
Basically, I need a way to onChange, the partial for the selected answer/comment.
alejandro babio is correct. render both partials. give one of them a style of display:none and then toggle the class in your coffescipt onchange handler. In the case that a user starts to type an answer and then switches to a comment you'll also want to clear the fields associated with the newly hidden fields otherwise on submit you could have both comment and answer submitted.
First at all, you must render both partials (answer_fields and comments_fields) with the form.
At the comments_partial replace <fieldset> with <fieldset style="display:none">
Change visibility of partials with coffeescript:
$('form').on 'change', '.change-type', (event) ->
$('.change-type').each ->
$(#).closest('fieldset').each ->
if $(#).is(':visible')
$(#).find('input').val('') # reset all filels to empty values
$(#).toggle()
I thing that do the trick.
I clear only input fields, but there must be all of them to prevent save de hidden element (like says hraynaud)
I haven't tested this, but perhaps you want to use dependent-fields-rails. Depending on the state of the select box you can then show either the comment box or answer box.
The example given on the github page (uses simple form, but you can apply the same to your form_for):
= simple_form_for(#filing) do
= f.input :registration_office, collection: ['habm', 'dpma']
.js-dependent-fields[data-select-id='filing_registration_office' data-option-value='habm']
= f.input :language, collection: ['english', 'german']
Then I guess you use fields_for to satisfy your has_many relationship for example:
f.select :switch_answer_table, collection: ['answer', 'comment']
.js-dependent-fields[data-select-id='question_switch_answer_table' data-option-value='answer']
f.fields_for :answers do |abuilder|
...
.js-dependent-fields[data-select-id='question_switch_answer_table' data-option-value='comment']
f.fields_for :comments do |cbuilder|
...
Again, not tested and a best guess but it hopefully nudges you into the right direction.
The partial fields are generated as a data attribute by the link_to_add_fields helper method. The js then gets the data object and renders it. Consider the following (this is untested but should be a good guide):
The easiest way would be to have seperate 'Add Answer' and 'Add Comment' links with seperate helper methods, or
Allow the user to select Answer or Comment before clicking the link to add and have each different partial as a different data attribute. e.g.
_form.html.erb
<select id="response_type_select_field">
<option value="comment">Comment</option>
<option value="answer">Answer</option>
</select>
<%= link_to_add_reply_fields "Add Reply", f, :answers, :comments %>
surveys_helper.rb
module SurveysHelper
def link_to_add_reply_fields(name, f, association_1, association_2)
object_1 = f.object.send(association_1).klass.new
object_1_id = object_1.object_id
object_1_fields = f.fields_for(association_1, object_1, child_index: object_1_id) do |builder|
render(association_1.to_s.singularize + "_fields", f: builder)
end
object_2 = f.object.send(association_2).klass.new
object_2_id = object_2.object_id
object_2_fields = f.fields_for(association_2, object_2, child_index: object_2_id) do |builder|
render(association_2.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_reply_fields", data: {id_1: object_1_id, id_2: object_2_id, object_1_fields: object_1_fields.gsub("\n", ""), object_2_fields: object_2_fields.gsub("\n", "")})
end
end
Coffee script:
jQuery ->
$('form').on 'click', '.add_reply_fields', (event) ->
time = new Date().getTime()
reply_type = $("#response_type_select_field option:selected").val()
if reply_type is 'answer'
data_attribute = 'object_1_fields'
regexp = new RegExp($(this).data('id_1'), 'g')
else if reply_type is 'comment'
data_attribute = 'object_2_fields'
regexp = new RegExp($(this).data('id_2'), 'g')
$(this).before($(this).data(data_attribute).replace(regexp, time))
event.preventDefault()
I have an application in which I'm using the gems rails3-jquery-autocomplete and nested_form to suggest existing users to be tagged as contributors to a project entry.
The form view:
<%= f.fields_for :contributors do |contributor|%>
<%= contributor.autocomplete_field :name, autocomplete_user_profile_last_name_projects_path, :update_elements => { :user_id => "#user_id" } %><br>
<%= contributor.hidden_field :user_id %>
<%= contributor.radio_button :contributor_type, 'Colleague' %>
<%= contributor.radio_button :contributor_type, 'Supervisor' %>
<%= contributor.radio_button :contributor_type, 'Client' %>
<%= contributor.link_to_remove "Remove this contributor" %>
<% end %>
<%= f.link_to_add "Add a contributor", :contributors %><br>
I have also overwritten the get_autocomplete_function(parameters) function in order to allow more than one column in my autocomplete, namely the columns first_name and last_name
def get_autocomplete_items(parameters)
items = UserProfile.select("DISTINCT CONCAT_WS(' ', first_name, last_name ) AS full_name, first_name, last_name, id, user_id").where(["CONCAT_WS(' ', first_name, last_name) LIKE ?", "%#{parameters[:term]}%"])
end
I also have the following line in my Projects controller:
autocomplete :user_profile, :last_name, :full => true, :extra_data => [:first_name, :last_name, :user_id ], :display_value => :fullname
With the `fullname' function simply being:
def fullname
"#{first_name} #{last_name}"
end
What I want to do is to get the value of user_id from the autocomplete items and put its value inside the hidden field, but when I check the server the it outputs something like:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"DYQ7iJeqdwMQrprSGn0WNHXY5iXDZLA5pUd3OlYJ2so=", "project"=>{"creator"=>"1", "title"=>"lesdodis", "contributors_attributes"=>{"0"=>{"name"=>"Test User the 1st", "user_id"=>"", "contributor_type"=>"Client", "_destroy"=>"false", "id"=>"21"}, "1"=>{"name"=>"Test User the 2nd", "user_id"=>"", "contributor_type"=>"Supervisor", "_destroy"=>"false", "id"=>"22"}, "1393677266696"=>{"name"=>"", "user_id"=>"", "_destroy"=>"1"}, "1393677267518"=>{"name"=>"", "user_id"=>"", "_destroy"=>"1"}}, "description"=>"asdad", "tag_list"=>"sdads", "link"=>"asdasd", "gallery_attributes"=>{"screenshots_attributes"=>{"0"=>{"description"=>"", "_destroy"=>"false", "id"=>"10"}}, "id"=>"16"}}, "commit"=>"Update", "id"=>"16"}
where the user_id field inside contributors is left blank. I have already tried manually assigning ids to the fields by using contributor.object.id to assign a unique number to the field name with limited success, as the new dynamically added fields do not have object ids. I am using this form for both creating and updating a project entry, so I would like to ask for help in how to make this work regardless of editing the field or adding a new one.
Update:
After a while of searching I finally got it to work by adding this code
<% field_name = "#{contributor.object_name}[user_id]".gsub(/(\])?\[/, "_").chop %>
<%= contributor.autocomplete_field :name, autocomplete_user_profile_last_name_projects_path, :update_elements => { :user_id => "##{field_name}" } %><br>
I hope this helps anyone who might encounter the same problem.
I've been wrestling with a similar problem as well. In your case, my feeling is that the nested_form helpers are generating unique identifiers which are defeating the "#user_id" selector passed to :update_elements.
You might be able to workaround it by hooking into the event streams for each gem and then manually updating the hidden :user_id for the specific contributor in the form. For the dynamically added contributors, possibly something like:
$(document).on('nested:fieldAdded', function(nf_event) {
var ac_input = nf_event.field.find('.ui-autocomplete-input');
ac_input.bind('railsAutocomplete.select', function(ac_event, data) {
$(this).next('input[type=hidden]').val(data.item.id);
});
});
For your contributors generated through existing associations (ie. editing), you can bind to their autocomplete inputs on $(document).ready(). This might at least get you pointed in the right direction.
After a while of searching I finally got it to work by adding this code
<% field_name = "#{contributor.object_name}[user_id]".gsub(/(\])?\[/, "_").chop %>
<%= contributor.autocomplete_field :name, autocomplete_user_profile_last_name_projects_path, :update_elements => { :user_id => "##{field_name}" } %>
I hope this helps anyone who might encounter the same problem.
I have one post with many pictures. In the one view I have two forms one with standard post fields and another to sending asynchronously pictures to pictures_controller, my JavaScript receives callback only with IDs of new added pictures, not yet associated with parent. How to add received by JavaScript many IDs to hidden field in post form and after sending in post_controller loop over the IDs from the hidden field and associate it with post model? I am trying to do Step 3 (the fun part) from: How to use JQuery-File-Upload to upload multiple images on one page during creating post?.
post view:
= form_for #post do |f|
.field
= f.label :title
= f.text_field :title
.field
= f.label :description
= f.text_area :description
= f.hidden_field :picture_ids
.actions
= f.submit 'Save'
= form_for #picture, :html => { :multipart => true, :id => "fileupload" } do |f|
= f.file_field :image, :multiple => true
%ul.thumbnails.files
JavaScript (I don't know how to add correctly newly received ids to hidden field, on start it contain only: []):
$('#fileupload').fileupload({
...
completed: function(e, data) {
console.log(data.result[0].picture_id); //e.g. pure: 505e1d811e7bf2b815000139, 505d80181e7bf2b8150000ab etc.
$("#post_picture_ids").val(data.result[0].picture_id);
});
});
post_controller:
def create
#post = Post.new(params[:post])
##post.picture_ids = params[:post][:picture_ids]
end
post:
{"utf8"=>"✓",
"authenticity_token"=>"FZA1rg53Qc21MCkT4YMDdIrkttmiRmdoGhPC7HS8Fx8=",
"notify"=>{"title"=>"",
"description"=>"",
"picture_ids"=>"505e1d811e7bf2b815000139"},
"commit"=>"Save"}
Mongoid throw error: undefined method 'compact' for #<Picture:0x007faf2513d810>
If your post has_many pictures mapping,
then your create controller code should be
#post.picture_ids = params[:post][:picture_ids]
instead of
#post.picture_ids = (Picture.find(received_post).id)
I have a set of dependent dropdown menus on my user/edit page. The user selects the parent dropdown, which then limits the selections available in the child dropdown. The user can add multiple additional sets of parent/child dropdowns. Product is the parent. Variety is the child.
This all works great unless a variety is entered into the database that includes double quotes. When that happens, it breaks the javascript and the trigger to render the partial doesn't work at all.
Single quotes do not cause this problem, nor do any other characters that I've tried. So this problem is specific to double quotes. I thought that escape_javascript was the answer here, but I've tried placing it in many different places in the code and nothing has worked. It's very possible that I just don't know exactly where the helper and it's parenthesis are supposed to go.
The api documentation is terrible. It says escape_javascript(). That's not helpful for me. Similarly, there isn't much clear guidance online. I've searched for hours.
Here are the relevant parts of my code:
Users#edit.html.erb
<%= render :partial => 'season', :collection => #user.seasons %>
Users#_season.html.erb
<% fields_for prefix, season do |season_form| -%>
<%= error_messages_for :season, :object => season %>
Product: <%= season_form.collection_select :product_id, Product.find(:all, :order =>'name'), :id, :name, {:prompt => "Select Product"}, {:onchange => "collectionSelected(this);"} %>
<% varieties = season.product ? season.product.varieties : Variety.all %>
<%= season_form.select :variety_id, options_from_collection_for_select(varieties, :id, :name, season.variety_id), :prompt => "This is optional" %>
Javascripts#dynamic_varieties.js.erb
var varieties = new Array();
<% for variety in #varieties -%>
varieties.push (new Array (<%=h variety.product_id %>, "<%=h variety.name %>", <%=h variety.id %>));
<% end -%>
function collectionSelected(e) {
product_id = e.getValue();
options = e.next(1).options;
options.length = 1;
varieties.each(function(variety) {
if (variety[0] == product_id) {
options[options.length] = new Option(variety[1], variety[2]);
}
});
}
Assuming you're just doing some escaping wrong (and that rails does it correctly), you could try something like:
var varieties = <%= #varieties.map { |v| [v.product_id, h(v.name), v.id] }.to_json %>
I'm following Railscast 88 to create a dynamic dependent dropdown menu. http://railscasts.com/episodes/88-dynamic-select-menus
I'm rendering these dropdowns inside a partial that I'm using in a multi-model form. The form I'm using follows the Advanced Rails Recipes process by Ryan Bates. Because I'm rendering the dropdown inside a partial, I had to depart from strictly following the Railscast code. On the Railscast link provided above, comments 30-31 and 60-62 address these issues and provide an approach that I used.
For new records, everything is working great. I select a parent object from the dropdown, and the Javascript dynamically limits the child options to only those items that are associated with the parent I selected. I'm able to save my selections and everything works great.
The problem is that when I go back to the edit page, and I click on the child selection dropdown, the constraints tying it to the parent object are no longer in place. I'm now able to select any child, whether or not it's connected to the parent. This is a major user experience issue because the list of child objects is just too long and complicated. I need the child options to always depend on the parent that is selected.
Here's my code:
Controller#javascripts
def dynamic_varieties
#varieties = Variety.find(:all)
respond_to do |format|
format.js
end
end
Views#javascripts #dynamic_varieties.js.erb
var varieties = new Array();
<% for variety in #varieties -%>
varieties.push(new Array(<%= variety.product_id %>, '<%=h variety.name %>', <%= variety.id %>));
<% end -%>
function collectionSelected(e) {
product_id = e.getValue();
options = e.next(1).options;
options.length = 1;
varieties.each(function(variety) {
if (variety[0] == product_id) {
options[options.length] = new Option(variety[1], variety[2]);
}
});
}
Views#users #edit.html.erb
<% javascript 'dynamic_varieties' %>
<%= render :partial => 'form' %>
View#users #_form.html.erb
<%= add_season_link "+ Add another product" %>
<%= render :partial => 'season', :collection => #user.seasons %>
view#users #_season.html.erb
<div class="season">
<% new_or_existing = season.new_record? ? 'new' : 'existing' %>
<% prefix = "user[#{new_or_existing}_season_attributes][]" %>
<% fields_for prefix, season do |season_form| -%>
<%= error_messages_for :season, :object => season %>
<div class="each">
<p class="drop">
<label for = "user_product_id">Product:</label> <%= season_form.collection_select :product_id, Product.find(:all), :id, :name, {:prompt => "Select Product"}, {:onchange => "collectionSelected(this);"} %>
<label for="user_variety_id">Variety:</label>
<%= season_form.collection_select :variety_id, Variety.find(:all), :id, :name, :prompt => "Select Variety" %>
</p>
<p class="removeMarket">
<%= link_to_function "- Remove Product", "if(confirm('Are you sure you want to delete this product?')) $(this).up('.season').remove()" %>
</p>
</div>
<% end -%>
Here's your culprit:
<%= season_form.collection_select :variety_id, Variety.find(:all),
:id, :name, :prompt => "Select Variety" %>
Works perfectly on a new record because it's showing everything, and gets overwritten when the select changes on the other select box.
You need to do something like this:
<% varieties = season.product ? season.product.varieties : Variety.all %>
<%= season_form.select :variety_id,
options_from_collection_for_select(varieties, :id,
:name, season.variety_id), :prompt => "Select Variety" %>
Which will use only the Varieties linked to season.product. If season.product doesn't exist it lists all of them. It will also automatically select the right one if the existing record had a variety_id.
It also wouldn't hurt to change.
<%= season_form.collection_select :product_id, Product.find(:all),
:id, :name, {:prompt => "Select Product"},
{:onchange => "collectionSelected(this);"} %>
to
<%= season_form.select :product_id,
options_from_collection_for_select(Product.find(:all),
:id, :name, season.product), {:prompt => "Select Product"},
{:onchange => "collectionSelected(this);"} %>
Which will select the proper product on page load. This second part is essentially the Rails way of doing what BYK's first suggestion was. However, given the nature of the onchange method given to the select box, this line on its own would not solve the problem. It would just enhance the user experience by highlighting the product associated with the season.
I think you have two options:
Give one of the products(or simply the first element of the product list) a "selected" attribute which will force the browser to select that one always.
Trigger the "collectionSelected" function on "dom ready" or "window.onload" with giving the product list selectbox as its parameter.
And a note: never, ever trust JavaScript to force the user to send proper data to the server.