Call rails function onChange - javascript

In my ruby on rails project, I have a form with a group of sliders that look like this:
<%= range_field(:subproject, :ans1, in: 0..100, id: 'slider1', :class => 'range range--light', :'data-init' => 'auto', :step => 1, :value => $project_value_1.to_i) %>
The total values of the sliders should always be equal to 100 fo rthe form to be submittable. Otherwise, an error message is displayed. All that is handled by this function:
$sumOfProjectValues = $project_value_1.to_i + $project_value_2.to_i + $project_value_3.to_i + $project_value_4.to_i + $project_value_5.to_i
if $sumOfProjectValues == 100
message = 'Project can be saved'
btnStyle = 'confirmation'
hideDisabled = 'none'
else
message = 'Project quota must add up to 100% before saving'
btnStyle = 'alert'
hideEnabled = 'none'
end
I would like to call this form validation function everytime a value changes (sort of like the onChange method in JS).
How do I do this?

try code:
$(".range").onChange(function(){
var sumOfProjectValues = $project_value_1.to_i + $project_value_2.to_i + $project_value_3.to_i + $project_value_4.to_i + $project_value_5.to_i
if sumOfProjectValues == 100
alert('Project can be saved')
else
alert('Project quota must add up to 100% before saving')
end
})

Related

How to differentiate between buttons in R/Shiny reactable javascript callback?

Take this example from the reactable documentation (interactive Shiny example provided at link):
data <- cbind(
MASS::Cars93[1:5, c("Manufacturer", "Model", "Type", "Price")],
details = NA
)
reactable(
data,
columns = list(
# Render a "show details" button in the last column of the table.
# This button won't do anything by itself, but will trigger the custom
# click action on the column.
details = colDef(
name = "",
sortable = FALSE,
cell = function() htmltools::tags$button("Show details")
)
),
onClick = JS("function(rowInfo, colInfo) {
// Only handle click events on the 'details' column
if (colInfo.id !== 'details') {
return
}
// Display an alert dialog with details for the row
window.alert('Details for row ' + rowInfo.index + ':\\n' + JSON.stringify(rowInfo.row, null, 2))
// Send the click event to Shiny, which will be available in input$show_details
// Note that the row index starts at 0 in JavaScript, so we add 1
if (window.Shiny) {
Shiny.setInputValue('show_details', { index: rowInfo.index + 1 }, { priority: 'event' })
}
}")
)
I want to include 2 buttons per details column cell, which I can do by changing the cell definition to:
cell = function() {
a <- htmltools::tags$button("Approve")
b <- htmltools::tags$button("Decline")
return(list(a,b))
}
But then how to differentiate between the Approve/Decline button within the JS() onClick() function? Is there another parameter I can pass that will give me this ability? I console.log'd both rowInfo and colInfo and could not find anything that seemed helpful to identify the two buttons. I'd like to have it so that I can return both:
Shiny.setInputValue('approve_button_click', ...)
and
Shiny.setInputValue('decline_button_click',...)
from the JS side so I can handle them separately in R. Any help is appreciated!
If you want to get just the row index you can do:
library(htmltools)
details = colDef(
name = "",
sortable = FALSE,
cell = function(value, rowIndex, colName){
as.character(tags$div(
tags$button("Approve", onclick=sprintf('alert("approve - %d")', rowIndex)),
tags$button("Decline", onclick=sprintf('alert("decline - %d")', rowIndex))
))
},
html = TRUE
)
In Shiny:
reactable(
data,
columns = list(
details = colDef(
name = "",
sortable = FALSE,
cell = function(value, rowIndex, colName){
as.character(tags$div(
tags$button(
"Approve",
onclick =
sprintf(
'Shiny.setInputValue("approve", %d, {priority: "event"})',
rowIndex
)
),
tags$button(
"Decline",
onclick =
sprintf(
'Shiny.setInputValue("decline", %d, {priority: "event"})',
rowIndex
)
)
))
},
html = TRUE
)
)
)

How to add a collection_select and text_area via JavaScript-function in Rails?

I want to create a table where rows and buttons can be added via a button (one for rows, one for columns). The columns consist of one collection_select (to select a device) as the title and text_field in each row (for test results for this device in each row).
The columns consist of several text_field and one text_area (for different test specifications).
I use this code to add a column in _form.html.erb:
<%= bootstrap_form_with(model: Testreport.new, local: true) do |f| %>
<table class="table" id="testreport_table">
...
<input type=button id='col_1_button' value="+" onclick="insertColumn();">
...
</table>
function insertColumn() {
let table = document.getElementById('testreport_table'),
columns_count = table.rows[1].cells.length,
rows_count = table.getElementsByTagName('tr').length,
i;
document.getElementById('button_row').colSpan = columns_count + 1;
<% a = f.collection_select( :devicesample_id, Devicesample.order(:name), :id, :name, include_blank: false, label: "Device Sample") %>
table.rows[1].insertCell(columns_count).innerHTML = '<%= a %>'
for (i = 2; i < (rows_count - 1); i++) {
table.rows[i].insertCell(columns_count).innerHTML = "Added Column";
}
return false;
}
However, rendering the page throws this error (Chrome): Uncaught SyntaxError: Invalid or unexpected token
The error is in this generated line:
table.rows[1].append('<select name="testreport[devicesample_id]" id="testreport_devicesample_id"><option value="4">Hisense 6486 LATAM</option>
Whole generated code:
function insertColumn() {
let table = document.getElementById('testreport_table'),
columns_count = table.rows[1].cells.length,
rows_count = table.getElementsByTagName('tr').length,
i;
document.getElementById('button_row').colSpan = columns_count + 1;
table.rows[1].insertCell(columns_count).innerHTML = '<div class="form-group"><label for="testreport_devicesample_id">Device Sample</label><select class="form-control" name="testreport[devicesample_id]" id="testreport_devicesample_id"><option value="4">Device A LATAM</option>
<option value="1">Device B </option>
<option value="3">Device C </option></select></div>';
for (i = 2; i < (rows_count - 1); i++) {
table.rows[i].insertCell(columns_count).innerHTML = "Added Column";
}
return false;
}
When I just copy the same exact collection_select to the regular body, it displays fine. What is causing this error? I have the same issue if I want to add a text_area, while text_field is working fine.
I am suspecting that it has something to do with the multi-line property of these fields, but even if that is the case I do not know how to avoid this.
I am new to Ruby, Rails and Javascript. I use:
ruby 2.6.5p114 (2019-10-01 revision 67812) [x64-mingw32]
Rails 6.0.2.1
Chrome Version 79.0.3945.130
(edit: replaced table.rows[1].append('<%= a %>');with table.rows[1].insertCell(columns_count).innerHTML = '<%= a %>'': which was the original code)
You get Uncaught SyntaxError: Invalid or unexpected token error because you have a multiline string in your code.
So change quotes from
table.rows[1].insertCell(columns_count).innerHTML = '<%= a %>'
to
table.rows[1].insertCell(columns_count).innerHTML = `<%= a %>`
You can also remove new lines:
table.rows[1].insertCell(columns_count).innerHTML = '<%= a.tr("\n","") %>'
Of course, it's better to move HTML generation to javascript side, but it's another topic :)
submitBtn.addEventListener("click",()=>{
//check to see the answer
const answer=getSelected();
if(answer){
if(answer === quizData[currentQuiz].correct){
score++;
}
currentQuiz++;
if(currentQuiz < quizData.length){
loadQuiz();
}
else{
quiz.innerHTML ='
<h2> you answered correctly at $
{score}/${quizData.length}
questions.</h2>
<button onclick="location.reload()">Reload</button>
';
}
}
});

Append a selected option to a selector

I have two selectors in ERB. They use the Chosen plugin:
<%= select_tag :provinces,
options_for_select(DataHelper::all_provinces_captions.zip(DataHelper::all_provinces_ids)),
{:multiple => true, class: 'chosen-select chzn-select',
:data => {:placeholder => 'Filter Provinces/States'}}
%>
<%= f.select :province_ids,
(DataHelper::all_provinces_captions.zip(DataHelper::all_provinces_ids)),
{ include_blank: true },
{multiple: true, data: {placeholder: 'Filter Provinces/States'} }
%>
I am trying to copy the options from one of the selectors to the other one, while keeping the selected options still selected, however it is not working. Here is the Javascript function I have tried:
var selectedVals = [];
$(".chzn-select").chosen().change(function() {
$("#provinces option:selected").each(function () {
console.log ("this value is " + ($(this).val()));
selectedVals.push($(this).val());
});
$("#education_plan_province_ids").empty();
for (var i = 0; i < selectedVals.length; i++) {
console.log (selectedVals[i] + " selected");
$("#education_plan_province_ids").append($("<option>" + selectedVals[i] + "</option>").attr('selected', true));
}
selectedVals = [];
});
Is there another alternative to attr('selected', true) ?
Here you go:
$(".chzn-select").chosen().change(function() {
$("#education_plan_province_ids").empty();
$("#provinces option:selected").each(function () {
$("#education_plan_province_ids").append($("<option>" + this.value + "</option>").prop('selected', true));
});
});
I am using prop here and getting rid of extra array (which I think is not needed but you can use it if you want). Also you had parenthesis in wrong place for option.

Why won't JS / JQuery read the text box values?

I have looked all over for a reason behind why this code does not work and I am stumped.
I have an ASPX page with C# code behind. The HTML mark-up has a JQuery dialog that functions properly. When the submit button is clicked the dialog closes and the data is passed to a web exposed method and is written to the database. All values are saved for the ddl and chkbox controls but the string value of the text box is empty. The database is set to NOT NULL for the field the text box is populating and the data is being saved so I know data is being passed but it is not the value entered into the text box.
The text box ID is txtCategoryName and the Client ID mode is set to static. I have tried to get the values using the following:
var CategoryName = $('#txtCategoryName').val();
var CategoryName = $('#txtCategoryName').text();
var CategoryName = $(document.getElementById('txtCategoryName')).text();
var CategoryName = $(document.getElementById('txtCategoryName')).val();
var CategoryName = document.getElementById('txtCategoryName').value;
All of these return the same blank field. I tried them one at a time.
Currently I am using this JS Code:
$(document).ready(function () {
var CategoryDialog = $(".EditCategories");
var BtnNew = $("#btnNew");
var CatDDL = document.getElementById("ddlCategoryParent3");
var CatChk = $("#chkCatActive").val();
var CategoryID = 0;
var CategoryName = $("#txtCategoryName").val();
var ParentID = CatDDL.options[CatDDL.selectedIndex].value;
if (CatChk) { CatChk = 1; } else { CatChk = 0; }
var CatDialog = $(CategoryDialog.dialog({
maxHeight: 1000,
closeOnEscape: true,
scrollable: false,
width: 650,
title: 'Category Editor',
autoOpen: false,
buttons: [
{
width: 170,
text: "Save",
icons: {
primary: "ui-icon-disk"
},
click: function () {
$(this).dialog("close");
window.alert(PageMethods.saveCat(CategoryName, ParentID, CategoryID, CatChk));
}
},
{
width: 170,
text: "Delete",
icons: {
primary: "ui-icon-circle-minus"
},
click: function () {
$(this).dialog("close");
}
},
{
width: 170,
text: "Cancel",
icons: {
primary: "ui-icon-circle-close"
},
click: function () {
$(this).dialog("close");
}
}
]
})
);
BtnNew.click(function () {
$(CatDialog).dialog('open');
$(CatDialog).parent().appendTo($("form:first"));
});
});
The code markup for the aspx page (categories.aspx)
<div class="EditCategories">
<div class="Table">
<div class="TableRow">
<div class="TableCell">
<div class="TextBlock220">Category Name </div>
</div><!-- End Table Cell -->
<div class="TableCell">
<input id="txtCategoryName" class="ControlTextBox" />
<!--<asp:TextBox ID="txtCategoryName" CssClass="ControlTextBox" runat="server" ClientIDMode="Static"></asp:TextBox>-->
</div><!--End Table Cell-->
</div><!-- End Row 1 -->
<div class="TableRow">
<div class="TableCell">
Parent Category
</div><!-- End Table Cell -->
<div class="TableCell">
<asp:DropDownList ID="ddlCategoryParent3" runat="server" CssClass="ControlDropDownList" ClientIDMode="Static"></asp:DropDownList>
</div><!--End Table Cell-->
</div>
<div class="TableRow">
<div class="TableCell">
Active
</div><!-- End Table Cell -->
<div class="TableCell">
<asp:Checkbox ID="chkCatActive" CssClass="ControlCheckBox" runat="server" ClientIDMode="Static"></asp:Checkbox>
</div><!--End Table Cell-->
</div><!-- End Row 3-->
</div>
</div>
The C# Code behind method for the ASPX page:
[System.Web.Services.WebMethod()]
[System.Web.Script.Services.ScriptMethod()]
public static string saveCat(string _Name_, int _parent_id_, int ID, int _Status_)
{
Category eCT = new Category();
eCT.CategoryName = _Name_;
eCT.ParentID = _parent_id_;
eCT.ID = ID;
eCT.Status = _Status_;
eCT.Save();
return eCT.resultMessage;
}
And the save method:
/// <summary>
/// If the ID = 0 the data is written as a new category.
/// If the ID is greater than 0 the data is updated.
/// </summary>
/// <returns>The objects result value will hold the result of the attempt to update data as type Boolean. The objects resultMessage value will contain the string result of the attempt to add data.</returns>
public void Save()
{
result = dl.CategoryExists(this);
if (result) { resultMessage = "The parent category already contains a category named " + CategoryName.Trim(); }
else {
if (ID > 0)
{
if (!result) { resultMessage = "There was an unexpected error updating " + CategoryName.Trim() + ". No changes were saved."; }
}
else
{
result = dl.InsertCategory(this);
if (!result) { resultMessage = "There was an unexpected error creating the Category."; }
}
}
if (result) { resultMessage = "New Category Successfully Created"; }
}
Any help is greatly appreciated thanks.
The issue here is you're attempting to get the value right as soon as the page loads, before the input field gets filled out. Place this code inside the button click function:
var CategoryName = document.getElementById('txtCategoryName').value;
and it should work for you. If not, let us know.
Your code should look something like this:
click: function () {
// note: CategoryID not used yet.
var CategoryName = $("#txtCategoryName").val();
var CatChk = $("#chkCatActive").val();
var CatDDL = document.getElementById("ddlCategoryParent3")
var ParentID = CatDDL.options[CatDDL.selectedIndex].value;
if (CatChk) { CatChk = 1; } else { CatChk = 0; }
$(this).dialog("close");
window.alert(PageMethods.saveCat(CategoryName, ParentID, CategoryID, CatChk));
}
You are fetching the values from your dialog at page startup time BEFORE they have been edited.
It looks like this:
var CategoryName = $("#txtCategoryName").val();
is run at page startup time before the page has been edited. This will fetch the default value for the input field and will never reflect any editing that is done on the page. The line of code above does not create a "live" connection with the input field on the page. It just gets the value at the time that line of code is run and from then on there is no connection to any edits made to the field.
I would think you want to fetch the value only later when you actually need to value for something. In general, you do not want to cache a value like this because the cached value gets out of sync with what is in the actual field on the page. Just fetch it at the very moment that you need it for something and it will never have a stale value.
If the place that you're using this value is inside the dialog click handler, then fetch it there so you are getting the latest value:
click: function () {
$(this).dialog("close");
var CatChk = $("#chkCatActive").val() ? 1 : 0;
var CategoryName = $("#txtCategoryName").val();
var CatDDL = document.getElementById("ddlCategoryParent3");
var ParentID = CatDDL.options[CatDDL.selectedIndex].value;
window.alert(PageMethods.saveCat(categoryName, ParentID, CategoryID, CatChk));
}

How to create Telerik MVC Combobox via JavaScript

I want to implement the ability to dynamically add comboboxes and I have to use Telerik ComboBox for that. I put this logic into button click.
$('#add-presenter').click(function (e) {
e.preventDefault();
var combobox = '#(Html.Telerik().ComboBox()
.Name("Presenters[" + (Model.Count) + "]")
.BindTo(new SelectList(LeaderList, "ID", "Value"))
.ClientEvents(ev => ev.OnChange("onSelect"))
.DataBinding(bnd => bnd.Ajax().Select("_LoadJournalist", "MonitoringFRadio"))
.Filterable(filter => filter.FilterMode(AutoCompleteFilterMode.StartsWith))
.HtmlAttributes(new { style = "width:320px;vertical-align:middle;" }))';
combobox = combobox.split('Presenters[' + index + ']').join('Presenters[' + (index + 1) + ']');
index++;
$('#presenters-block').append(combobox);
}
This code renders in browser as this:
$('#add-presenter').click(function (e) {
e.preventDefault();
var combobox = '<div class="t-widget t-combobox t-header" style="width:320px;vertical-align:middle;"><div class="t-dropdown-wrap t-state-default"><input class="t-input" id="Presenters[0]-input" name="Presenters[0]-input" type="text" /><span class="t-select t-header"><span class="t-icon t-arrow-down">select</span></span></div><input id="Presenters[0]" name="Presenters[0]" style="display:none" type="text" /></div>';
combobox = combobox.split('Presenters[' + index + ']').join('Presenters[' + (index + 1) + ']');
index++;
$('#presenters-block').append(combobox);
combobox = $('#Presenters\\['+index+'\\]').data('tComboBox');
});
The problem is in data-binding. This code generates proper HTML, but newly added list doesn't "drop"
When I do combobox = $('#Presenters\\['+index+'\\]').data('tComboBox'); for newly added item I get undefined (it exists, but data isn't set), so combobox.dataBind(dataSource) approach doesn't work.
Ok, I tried, but failed to do this without postback. Here's rough solution to the problem: do ajax request and replace content with partial view:
The Partial view:
#model List<int>
#{
var LeaderList = ViewData["LeaderList"] as List<ListItem>;
}
<div id="presenters-ajax-wrapper">
<div id="presenters-block">
#(Html.Telerik().ComboBox()
.Name("Presenters[0]")
.BindTo(new SelectList(LeaderList, "ID", "Value"))
.ClientEvents(ev => ev.OnChange("onSelect"))
.DataBinding(bnd => bnd.Ajax().Select("_LoadJournalist", "MonitoringFRadio"))
.Filterable(filter => filter.FilterMode(AutoCompleteFilterMode.StartsWith))
.HtmlAttributes(new { style = "width:320px;vertical-align:middle;" }))
#for(int i=1; i<Model.Count; i++)
{
var item = LeaderList.FirstOrDefault(l => l.ID == Model[i]);
var value = item != null ? item.Value : "";
#(Html.Telerik().ComboBox()
.Name("Presenters[" + i + "]")
.Value(value)
.BindTo(new SelectList(LeaderList, "ID", "Value"))
.ClientEvents(ev => ev.OnChange("onSelect"))
.DataBinding(bnd => bnd.Ajax().Select("_LoadJournalist", "MonitoringFRadio"))
.Filterable(filter => filter.FilterMode(AutoCompleteFilterMode.StartsWith))
.HtmlAttributes(new { style = "width:320px;vertical-align:middle;" }))
}
</div>
<button id="add-presenter" class="t-button">+</button>
<script type="text/javascript">
var index = #(Model.Count == 0 ? 0 : Model.Count-1);
$('#add-presenter').click(function (e) {
e.preventDefault();
index++;
var msg = $('#monitForm').serialize();
$.ajax({
url: '#Url.Action("_GetPresenters","MonitoringFRadio")'+'?count='+(index+1),
data: msg,
type: 'POST',
success: function(data) {
$('#presenters-ajax-wrapper').html(data);
}
});
});
</script>
</div>
Action:
[HttpPost]
public virtual ActionResult _GetPresenters(EditableMonitoring model, int count)
{
//some logic here...
return PartialView("EditorTemplates/Presenters", model.Presenters);
}
Well, probably it would be better to create another partial view which would render a single combobox, instead of redrawing all of them...

Categories