I have the following issue. Since there're many, many business rules I've decided to enter those functions as js to update automatically.
In my view i have:
this is inside a table
(<%= javascript_include_tag 'people'%>)
<th>Name</th>
<th>Money</th>
<th>Money Comment</th>
</tr>
<% #counter = 0 %>
<% #people.each do |p| %>
<% #counter += 1 %>
<tr>
<%= form_for(:p) do |f| %>
<th><%= f.label(p.name) %></th>
<th><label for="<%= "money_" + #counter.to_s %>"> <%= f.select(:money, 1..10, :selected => p.money)%></label></th>
<th><label for="<%= "comment_" + #counter.to_s %>"> <%= f.label(p.money_comment, :disabled => 'disabled')%></label></th>
<% end %>
<% end %>
and in the javascript the function looks like this:
function change_people() {
money1 = $('money_1').getValue();
if (money1 == 10) {
$('comment_2').setValue("I know what you're doing you mofo");
}
else {
$('comment_2').setValue("");
}
}
document.observe('dom:loaded', function() {
change_people();
$('money_1').observe('change', change_people);
});
but for some reason it keeps telling me it doesn't know what I mean by "money_1". I check the html source and is compiling the view, am I referencing the element wrongly in js?
Thanks for the help!
It looks as if you need a selector:
<th id="<%= "money_" + #counter.to_s %>">
<label for="<%= "money_" + #counter.to_s %>">
<%= f.select(:money, 1..10, :selected => p.money)%>
</label>
</th>
Now you've given Prototype the ability to search and find the correct element (the select with the value you're looking for), which you pass to the $() function to call getValue():
function change_people() {
// Note the "#" on #money_1, which is an ID selector
// the space " " between is a descendant element selector
money1 = $($$('#money_1 select')).getValue();
if (money1 == 10) {
// do stuff
} else {
// do other stuff
}
}
If you're looking to iterate over all of the money select values in the table, you would instead want to use a class; you are only allowed to have one element per ID, but a class by rule is grouped by attribute. The difference would be:
<th class="money_select">
<label for="<%= "money_" + #counter.to_s %>">
<%= f.select(:money, 1..10, :selected => p.money)%>
</label>
</th>
And then in your javascript:
function change_people() {
// Note the "." on .money_1, which is a class selector
// the space " " between is a descendant element selector
$$('.money_select select').each(function(element){
if ($(element).getValue() == 10) {
// do stuff
} else {
// do other stuff
}
});
}
Related
I have 4 models, User, Products, Property & ProductProperty. Here's how they relate:
All attributes in these models, such as name, value, upc, and available_on, must have a presence of value!
Once a user is created successfully, they can create products (with the Products model) that can have many properties (with the Property model) and a corresponding value (with the ProductProperty model). And I have to create all of these in a single form.
With the help of some tutorials and Reddit users, I was able to successfully implement the product form which is able to create a new product, property, and corresponding value. Here's the snap of my implementation:
Now here comes the tricky part: to add more properties dynamically to create multiple properties and their values of a product. So here's my implementation of it so far:
Product Form
<%= form_with model: #product do | prod | %>
<%= prod.label :name %>
<%= prod.text_field :name %>
<br>
<%= prod.label :upc, "UPC" %>
<%= prod.text_field :upc %>
<br>
<%= prod.label :available_on %>
<%= prod.date_field :available_on %>
<br>
<h3>Properties</h3>
<table>
<thead>
<tr>
<th>Property Value</th>
<th>Property Name</th>
</tr>
</thead>
<tbody class="fields">
<%= prod.fields_for :product_properties do | prod_prop | %>
<%= render "property", prod_prop: prod_prop %>
<% end %>
</tbody>
</table>
<br>
<%= link_to_add_fields('Add More Properties', prod, :product_properties) %>
<br>
<%= prod.submit %>
<% end %>
Property Form Partial
<tr>
<td><%= prod_prop.text_field :value %></td>
<%= prod_prop.fields_for :property do | prop | %>
<td><%= prop.text_field :name %></td>
<% end %>
</tr>
Helper Method
def link_to_add_fields(name, prod, association)
new_object = prod.object.send(association).klass.new
id = new_object.object_id
fields = prod.fields_for(association, new_object, child_index: :id) do | builder |
render "property", prod_prop: builder
end
link_to(name, "#", class: 'add_fields', data: {id: id, fields: fields.gsub("\n", "") } )
end
application.js file:
$("form").on("click", ".add_fields", function (event) {
let regexp, time;
time = new Date().getTime();
regexp = new RegExp($(this).data("id"), "g");
$(".fields").append($(this).data("fields").replace(regexp, time));
return event.preventDefault();
});
Now when I click on the "Add more Properties" link it adds only one field:
Issue 1: I believe I need to do some modifications in the helper method to implement this correctly but I am not able to figure out the logic of it.
There's one more issue! When we create additional property instances of property name & value, they will all disappear if there're any validation errors:
Product Controller:
def new
#product = Product.new
#product.product_properties.build.build_property
end
def create
#product = current_user.products.new(product_params)
if #product.valid?
#product.save
redirect_to products_path, notice: "Success!"
else
render 'new', status: :unprocessable_entity
end
end
I can think of a couple of solutions to this issue: first, to implement the logic in the create action for the dynamically created property name and value fields to persist between requests, second, to use javascript.
Issue 2: So how do I make the additional properties created by the user persist in the view between requests?
I am running rails v7.0.4 and I am not allowed to use hotwire, only Javascript.
Guys I've search for a solution for this problem with no luck, wish if someone could help.
I'm building nodejs express app, in one of .ejs I'm trying to compare mongoDB Id of the selected item with the id passed in the query string in order to change the selected item accordingly.
Below is the code:
<table class="table table-striped">
<tr>
<th>Available Models</th>
</tr>
<script>
<% var index = 0; %>
</script>
<% if(modelId) { %>
<script>
for(i =0; i <= document.getElementById("mnumake").options.length;i++) {
var optionValue = document.getElementById("mnumake").options[i].value;
if (<% modelId.equals(%> optionValue <% ) %> ) {
index = i;
document.getElementById("mnumake").options[i].selected = true;
break
}
}
</script>
<% } %>
<% makes[index].models.forEach(function(model){ %>
<tr><td><%= model.name %></td></tr>
<% }) %>
</table>
I really cannot find a way to describe this. Essentially I have a database that can have any number of tables with different column lengths and names. I have a table which defines each table and it's columns. I have a query to search for values within these tables, and I pass the results of the search and the columns to ejs using express. What I need is to echo the results. I do have:
<div class="row">
<table>
<thead>
<tr>
<% columns.forEach(function(column) { %>
<th><%= column %></th>
<% }); %>
</tr>
</thead>
...
This outputs the name of the columns in the table header correctly. I cannot for the life of me figure out how to print the actual results however. I have tried many different ways but all I keep getting is undefined or [Object object]. I have this currently:
<tbody>
<% for(var r = 0; r < results.length; r++) { %>
<tr>
... need to access column here ...
</tr>
<% } %>
</tbody>
I first tried the following (inside above)
<% for(var key in Object.keys(results[r])) { %>
<%= results[r].key %>
<% } %>
followed by so many different attempts along the lines of this. I suppose the issue is not knowing the possible key names. I don't even know what to search for either to be entirely honest. My mind is drawing a blank.
Any help is much appreciated.
Object.keys returns an array of all the keys in an object. You can use forEach to iterate over those keys, to access the value of the object via object[key]
Also, you can use forEach on your results instead of for to keep things clean.
<% results.forEach(function (result) { %>
<tr>
<% Object.keys(result).forEach(function (key) { %>
<td><%= result[key] %> </td>
<% }) %>
</tr>
<% }) %>
Modern browsers have arrows now, so this can be simplified a little:
<% results.forEach(result => { %>
<tr>
<% Object.keys(result).forEach(key => { %>
<td><%= result[key] %> </td>
<% }) %>
</tr>
<% }) %>
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.
[Sorry for long question but it is necessary to explain the problem]
I am working on a learning website and it is supposed to show a list of messages to user if there are any. Something like this:
When user presses close button, that message must be marked "read" and should not be shown next time. Following code is used to generate those messages:
<% foreach (var m in Model.UserMessages) { %>
<div class="um" id="m<%=Html.AttributeEncode(m.ID) %>">
<p class="mh"><%= Html.Encode (String.Format ("From {0} ({1})", m.From, m.Sent)) %></p>
<p><%= Html.Encode (m.Text) %></p>
<% using (Html.BeginForm ("CloseMessage", "Home", new { id = m.ID })) { %>
<input type="submit" value="Close<%= Html.AttributeEncode (m.ID) %>" id="c<%= Html.AttributeEncode (m.ID) %>"/>
<% } %>
</div>
<% } %>
After that, following the guidelines, I added the support of http post method in controller which marks the message as read and then refreshes the view (to handle disabled JavaScript):
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CloseMessage (int id) {
using (var dl = new DL ()) {
dl.MarkAsRead (id);
}
if (Request.IsAjaxRequest ()) {
return new EmptyResult ();
}
else {
return RedirectToAction ("Index");
}
}
Then I wanted to add JavaScript support such that only the message goes away (using jQuery). But the problem is that I am generating the buttons and messages programmatically.
So ended up with a weird looking javascript code in viewpage:
<script type="text/javascript">
$().ready(function() {
<% foreach (var m in Model.UserMessages) { %>
$("#c<%=Html.AttributeEncode (m.ID) %>").click(function(event) {
$.post("Home/CloseMessage/<%=Html.AttributeEncode (m.ID) %>");
$("#m<%=Html.AttributeEncode (m.ID) %>").slideUp("slow");
event.preventDefault();
});
<% } %>
});
</script>
This is basically creating the javascript code in a C# loop which actually works but too much for me to digest. Is there any better way of doing this?
You could just create one javascript function that takes the element IDs as parameters :
function myFunction(ID) {
$.post("Home/CloseMessage/" + ID);
$("#m" + ID).slideUp("slow");
}
And :
<% foreach (var m in Model.UserMessages) { %>
<div class="um" id="m<%=Html.AttributeEncode(m.ID) %>">
<p class="mh"><%= Html.Encode (String.Format ("From {0} ({1})", m.From, m.Sent)) %></p>
<p><%= Html.Encode (m.Text) %></p>
<% using (Html.BeginForm ("CloseMessage", "Home", new { id = m.ID })) { %>
<input type="submit" value="Close<%= Html.AttributeEncode (m.ID) %>"
id="c<%= Html.AttributeEncode (m.ID) %>"
onclick="myFunction('<%=Html.AttributeEncode(m.ID)%>')"/>
<% } %>
</div>
<% } %>
YOu could uses a CSS selector in your JS to get all buttons with a particular class assigned to them. Then you can wire up you click event to them. So your HTML would be something like this:
<% foreach (var m in Model.UserMessages) { %>
<div class="um" id="m<%=Html.AttributeEncode(m.ID) %>">
<p class="mh"><%= Html.Encode (String.Format ("From {0} ({1})", m.From, m.Sent)) %></p>
<p><%= Html.Encode (m.Text) %></p>
<% using (Html.BeginForm ("CloseMessage", "Home", new { id = m.ID })) { %>
<input type="submit" class="MyCloseButton" value="Close<%= Html.AttributeEncode (m.ID) %>" id="c<%= Html.AttributeEncode (m.ID) %>"/>
<% } %>
</div>
<% } %>
I've only added the class="MyCloseButton" to your input
Then in your JS (I use mootools but JQuery has CSS selectors too but you will need to port it sorry) you can do something like this:
window.addEvent( "domready", function() {
$$("input.MyCloseButton").each( function( button ) {
// Add your code here, you can reference button.id etc for each of your buttons
}
});
This way you only have to write out one little function in plan JS and not worry about doing it server-side, all you need to do it decorate your buttons with a css class so the JS can find them.
I understand its not pretty, but its not that bad either. I think there are easier ways to do this, however.
You can use jquery's positional selectors from the button's click event handler. Then just assign the same handler to every button.
<script type="text/javascript">
$(document).ready(function() {
$("input[type=submit]").click(function(){
$(this).parent().slideup("slow");
$.post("Home/CloseMessage/" + $(this).parent().attr("id"));
event.preventDefault();
});
});
});
</script>