I am trying to dynamically add a text box based on the selection of dropbox. When the user selects 'other' a text box gets generated asking them to explain the other. The user can dynamically add mutiple dropboxes, resulting in multiple text boxes if other is selected value in dropbox. Every generated dropbox has a unique name which gets read and placed in id of dynamically generated textbox.
The problem i am facing is that when there are multiple dropboxes and the first dropbox selection is something other that 'Other' and second dropbox value is other, the text box generated is placed in front of the first dropbox where it should be placed in front of the dropbox relevant to it.
The html code is as follows for the dropbox:
<div id="container">
<label id="rightlabel"for="dropbox1"></label>
<span>
<select id="frequency" onclick="getData(this, name)" name="dropbox[4fb103e3-06e7-4c88-8836-73b855968478]"></select>
<span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for="dropbox[4fb103e3-06e7-4c88-8836-73b855968478]"></span>
</span>
<div id="hiddenothertexbox"></div>
</div>
Javascript is as follows:
function getData(title, i) {
var value = title.options[title.selectedIndex].value;
var y = i.replace(/-/g, '')
$('#hiddenother').attr('id', y);
if (value == 'Other') {
str = '<label id="leftlabel">if other, please specify</label><span><input id="textboxid" class="text-box single-line" type="text" value="" name="textbox ></input></span>';
$('#'+y).html(str);
}
else {
str = '';
$('#'+y).html(str);
}
}
teh javascript gets the names of the dropbox and replaces the id of 'hiddenanothertextbox' with that id so its unique. I have an idea of the problem, i think its because when the user does not click teh first dropbox the id of 'hiddenanothertextbox' does not change for the first dropbox and when another dropbox is added and the value is changed, the hiddenanothertextbox for first dropox value changes adding it in front of first not second. I am struggling to achieve teh required result.
UPDATED JAVASCRIPt
function getData(title, i) {
var value = $(title).val();
var y = i.replace(/-/g, '');
$('#hiddenother').attr('id', y);
if (value == 'Other') {
str = '<label id="leftlabel">if other, please specify</label><span><input id="textboxid" class="text-box single-line" type="text" value="" name="textbox"" ></input></span>';
$(title).after(str);
}
else {
$(title).nextUntil('#textboxid').remove();
}
}
Working Demo
Use this code snippet.
var flag = 0;
function getData(title, i) {
var value = $(title).val();
var y = i.replace(/-/g, '');
$('#hiddenother').attr('id', y);
if (value == 'Other' ) {
if(flag == 0){
flag=1;
str = '<label id="leftlabel">if other, please specify</label><span><input id="textboxid" class="text-box single-line" type="text" value="" name="textbox"></input></span>';
$(title).after(str);
}
}
else {
flag=0;
$(title).nextUntil('#textboxid').remove();
}
}
Html :
<select id="frequency" onchange="getData(this, name)" name="dropbox[4fb103e3-06e7-4c88-8836-73b855968478]">
Changes :
var value = $(title).val() gives you the correct value
Use onchange="getData(this, name)" instead of onclick=".."
In variable str : name="textbox" did not have ending "
use .after() or .append() instead of .html()
Related
For my automated test using selenium in C#, I want to select a drop down list item (any) from the type field (ref attached image), however since the id's/xpath for selected element getting generated dynamically at every page load, my selected element click is failing when I run my recorded script.
For example my below script fail, as XPath "//select[#name='Entries[db9ef219-0f54-4925-9589-0f39351f44a4].TypeID']" changes every time as I run test in VS.Net. Value db9ef219-0f54-4925-9589-0f39351f44a4 changes every time when page loads.
IWebElement selectType =
driver.FindElement(By.XPath("//select[#name='Entries[db9ef219-0f54-4925-9589-0f39351f44a4].TypeID']"));
selectType.Click();
Here is the page code - on load for each new row a unique value (below it's c36582c1-131a-4f6f-8711-390048f5779f) is generated and stored under class RegEffEntryContainer, and is used for each list elements - type/description ( e.g. id="Entries_c36582c1-131a-4f6f-8711-390048f5779f__TypeID", id= "Entries_c36582c1-131a-4f6f-8711-390048f5779f__Organisation")
Any help in resolving this would be highly appreciated - thanks in advance!
FYI: If I use the below code using Dynamic XPath, it will only work for the first row and I would not be able to record any subsequent row entries via the script. Requirement is to enter all 3 entrees before clicking Save/Submit button (not shown on the screenshot above).
IWebElement selectType = driver.FindElement(By.XPath("//select[contains(#id, '__TypeID')]"));
Thank you #JeffC for your suggestion - adding the html code as suggested - I've still kept the code image provided earlier.
<div class="RegEffEntryContainer" xpath="1">
<div class="row">
<div class="col-11">
<input type="hidden" name="Entries.index" autocomplete="off" value="21b28f6b-8aaa-4924-815a-1d925585fa36">
<input data-val="true" data-val-number="The field EntryID must be a number." data-val-required="The EntryID field is required." id="Entries_21b28f6b-8aaa-4924-815a-1d925585fa36__EntryID" name="Entries[21b28f6b-8aaa-4924-815a-1d925585fa36].EntryID" type="hidden" value="391">
<div class="row">
<div class="col-12 col-sm-6 col-lg-3">
<div class="field-wrapper">
<select class="form-control valid" data-val="true" data-val-number="The field TypeID must be a number." id="Entries_21b28f6b-8aaa-4924-815a-1d925585fa36__TypeID" name="Entries[21b28f6b-8aaa-4924-815a-1d925585fa36].TypeID" required="" aria-describedby="Entries_21b28f6b-8aaa-4924-815a-1d925585fa36__TypeID-error" aria-invalid="false">
<option value="">Select type</option>
<option value="1">Regulatory Activity</option>
<option value="2">Major Project</option>
<option value="3">Other Activities</option>
</select><span class="asterisk">*</span>
</div>
</div>
Update[27/02/2019 7:00 PM] - since the numeric id's (value: 21b28f6b-8aaa-4924-815a-1d925585fa36 above) used for TypeID/OrganisationID is dynamically created at page load, is there a way using Javascript to record this in a variable at page load and reuse that to create a XPath for element identification later?
If you know the exact count of the row then simply use this
IWebElement firstSelect = driver.FindElement(By.XPath("//select[contains(#id, '__TypeID')][1]"));
IWebElement secondSelect = driver.FindElement(By.XPath("//select[contains(#id, '__TypeID')][2]"));
IWebElement thirdSelect = driver.FindElement(By.XPath("//select[contains(#id, '__TypeID')][3]"));
This might not be a perfect answer, or the best way to do this, but I was able to solve this issue using JavaScript. Used an Array to store the dynamic ids at run time, and later used them to create string to find the required element on DOM. Below is the my code.
int numberOfEntriesOnPage = System.Convert.ToInt32(((IJavaScriptExecutor)driver).ExecuteScript("return document.getElementsByName('Entries.index').length"));
string[] array = new string[numberOfEntriesOnPage];
for (int a = 0; a < numberOfEntriesOnPage; a++)
{
String script = "return document.getElementsByName('Entries.index')[" + a + "].value";
array[a] = ((IJavaScriptExecutor)driver).ExecuteScript(script).ToString();
Console.WriteLine("Array value:" + array[a]);
string rowTypeID = "Entries_" + array[a] + "__TypeID";
select_select_by_index(By.Id("Entries_" + array[a] + "__TypeID"), 1);
IWebElement selectOrg = find_element(By.Name("Entries[" + array[a] + "].OrganisationID_input"));
selectOrg.Clear();
selectOrg.SendKeys("3801 LTD");
IWebElement selectInOffice = driver.FindElement(By.Id("Entries_" + array[a] + "__InOffice"));
selectInOffice.Clear();
selectInOffice.SendKeys("10");
IWebElement selectOffsite = driver.FindElement(By.Id("Entries_" + array[a] + "__Offsite"));
selectOffsite.Clear();
selectOffsite.SendKeys("5");
IWebElement comments = driver.FindElement(By.Id("Entries_" + array[a] + "__Comment"));
comments.Clear();
comments.SendKeys(array[a] + "Manish test");
IWebElement save = find_element(By.XPath("//button[#value='SaveDraft']"));
save.Click();
}
public static void select_select_by_index(By by, int index)
{
var select = new SelectElement(find_element(by));
select.SelectByIndex(index);
}
I'm working in MVC.
I have a table with a bunch of rows. One cell contains an 'Order' button, and a different cell contains a numeric input where the user can specify the quantity of the item he wants to buy? How do I pass through the quantity value in my ActionLink from the input cell? The issue here is not in finding a text value, but rather finding the text value of a specific row
Table Cells
<td><input id="itemQuantity" type="number" value="1" min="1" max="#item.QuantityInStock" size="10" class="universalWidth"/></td>
<td>#Html.ActionLink("Order", "OrderTuckShop", new { id = item.IngredientId, quanity = ???}, new { #class = "btn btn-success universalWidth" })</td>
Method Call
public ActionResult OrderTuckShop(Guid? id, int quantity)
You cannot add the value of the textbox in your ActionLink() method because its razor code, and that is parsed on the server before its sent to the client. In order to respond to client side events you need to use javascript/jquery.
Create a manual link and add the IngredientId as a data- attribute and add an additional class name to use as a selector
<td>
Order
</td>
In addition, remove the id="itemQuantity" in the input (duplicate id attributes are invalid html) and add a class name as a selector
<input class="quantity" type="number" ... />
Then include the following script to read the value of the input and make a redirect
var baseUrl = '#Url.Action("OrderTuckShop")';
$('.order).click(function() {
var id = $(this).data('id');
var quantity = $(this).closest('tr').find('.quantity').val();
location.href = baseUrl + '?id=' + id + '&quanity=' + quanity;
});
However, the name of the method suggests you perhaps should be making a POST, not a GET?
I have a small project with an EditorTemplate.
I show some items which are initially in a List<T> but I want to be able
to add Items when the user presses a Button.
normally I add the items to the View like this
#for (int i = 0; i < Model.Models.Count; i++)
{
#Html.EditorFor(model => model.Models[i], "_AddArticleFullQuantity");
}
When I want to add items dynamically I tried to
create a button which uses ajax to call the server
<button id="addButton" type="button" class="btn btn-default btn-block" onclick="m_GUIRequests.AddArtikelToDiv()">add Article</button>
GUIRequests.prototype.AddArtikelToDiv = function ()
{
this.Request.CallAjax("/NewItemDelivery/GetPartialView_AddArticleFullQuantity", "", GUIRequests.AddToView);
}
GUIRequests.AddToView = function (html) {
$("#addedItems").append(html);
}
The button makes an ajax call to my controller which will do the following
public ActionResult GetPartialView_AddArticleFullQuantity()
{
WrongItemsReceivedModel model = new WrongItemsReceivedModel();
ModelContainer<WrongItemsReceivedModel> container = (ModelContainer<WrongItemsReceivedModel>)TempData["ModelContainer"];
container.Add(model);
return PartialView("~/views/Shared/EditorTemplates/_AddArticleFullQuantity.cshtml", container.Models[0]);
}
And in the end I get what I expected it will show me my template BUT the items initially shown from the List are numbered
So normally I have elements like:
<input class="form-control col-md-6 text-box single-line" data-val="true" data-val-required="MESSAGE" id="Models_0__ModelNumberID" name="Models[0].ModelNumberID" onchange="m_GUIRequests.SetWrongItemsReceivedValues()" type="text" value="">
But I get this:
<input class="form-control col-md-6 text-box single-line" data-val="true" data-val-required="MESSAGE" id="ModelNumberID" name="ModelNumberID" onchange="m_GUIRequests.SetWrongItemsReceivedValues()" type="text" value="">
I think its because I add one with the EditorFor "command" but the other one is added as PartialView.
Is there any way how I can add an EditorFor element so that my logic won't break ?
For editing a variable length list in ASP.NET MVC I would recommending reading the following article. It presents a very clean approach to implement this. On the server you will not need any TempData for persistence and also it illustrates the usage of a nice little helper that will allow you to generate the proper input field names.
As far as your question is concerned, you are correct that the reason why you get wrong input names is because when you return the partial view directly from the controller action, it no longer has the parent context of the Editor Template. There are some ways to circumvent this but it's very hacky and I would rather recommend the approach presented in the article.
Normally i would go for steven sanderson's blog post as Darrin mention as it has become as one of the the industry's standard. Yes partial view is a pain in your scenario.
In your scenario where you would want to keep editor template logic and dynamic added elements linked i would go and generate same name as editor for requries as below
This is my code just to give you the example.
$("#addItemdynamically").click(function () {
$.ajax({
url: '#Url.Action("GetNewGuid", "NewWebForms")',
cache: false,
success: function (newguid) {
id = newguid;
var html = '<tr class="editorRow">' +
'<td><input type="radio" id="Selected" name="Selected" value=' + id + ' /></td>' +
'<td><input type="hidden" name=\OptionsList.Index value=' + id + ' /></td>' +
'<td><input type="text" name=\OptionsList[' + id + '].Text /></td>' +
'<td><input type="hidden" name=\OptionsList[' + id + '].guid value=' + id + ' /></td>' +
'<td> delete</td>' +
'</tr>'
$("#editorRows tbody").append(html);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
}
});
return false;
});
Basically my new guid is getting newly generated guid from the server side and appending it to the row which is generated by pressing add new item button.
you can generate int digit if you like in here but that also require some other hack.
I have a form which asks users for their personal info and their family members.
fields of the family members section is repeating.
my question is what is best practice to handle these kind of repeating forms?
I currently use AJAX to repeat forms but how to collect data from these repeating fields?
since some one asked for how I repeat form, I do it like this:
AJAX Call
$(document).on('click', '.btn-add-item', function (e) {
e.preventDefault();
var $results = $('#results');
$.ajax({
url: '/AJAX/AddFamilyForm',
type: 'post',
success: function (data) {
$(data).appendTo($results);
afterAJAX();
}
});
});
C# code
[HttpPost]
public PartialViewResult AddFamilyForm()
{
if (!Request.IsAjaxRequest()) return null;
return PartialView("_FamilyForm");
}
This is some skeleton code on how to get this to work with proper model-binding in MVC. You'll need to write some JS to be able to delete/add new rows.
Model
public class MyModel
{
public FamilyMembers[] FamilyMembers { get; set; }
}
View
<button id="addNewFamilyMember" type="button">Add</button>
#if (Model.FamilyMembers != null)
{
for (int i = 0; i < Model.FamilyMembers.Length; i++)
{
<tr>
<td>
<button type="button">Delete</button>
#Html.Hidden("FamilyMembers.Index", i)
</td>
<td>
#Html.TextBoxFor(m => Model.FamilyMembers[i].Relation)
</td>
<td>
#Html.TextBoxFor(m => Model.FamilyMembers[i].FullName)
</td>
</tr>
}
}
Below is the code for adding a new member. It creates html dynamically and is able to bind to the posted model because of naming conventions. time gives each added row a unique id so all the data stays together.
JS (using Jquery)
var hidden = '#Html.Hidden("FamilyMembers.Index", "{id}")';
var relationHtml = '#Html.TextBox("FamilyMembers[{id}].Relation")';
var fullNameHtml = '#Html.TextBox("FamilyMembers[{id}].FullName")';
$("#addNewFamilyMember").on("click", function () {
var time = Date.now();
var deleteHtml = "<button type='button'>Delete</button>";
$("#familyMembers-table").find("tbody")
.append($("<tr><td>" + hidden.replace("{id}", time) + deleteHtml + "</td>" +
"<td>" + relationHtml.replace("{id}", time) + "</td>" +
"<td>" + fullNameHtml.replace("{id}", time) + "</td></tr>"));
});
One of the solution could be combination of hidden field and control name.
Steps:
Use a hidden field to keep the count the number of row.
Create controls with name like text_relation_1 for first row and text_relation_2 for second row and so on
Generate other controls in same way.
Increase and decrease the hidden field value so that when values post you can know the number of rows added by the user
On your action use FormCollection and loop though hidden field number and get the values from FormCollection
Like suppose I created 3 rows then I can create a action like below
public ActionResult SomeActionMethod(FormCollection formCollection, string hid)
{
for(int i=1;i<hid;i++)
{
var relationId="text_relation_"+i;
var firstrealtion=formCollection[relationId];
...
}
}
You don't need any extra Ajax requests for this, since you can use established and standard <form> features.
Just append [] to the name of the added forms and you'll end up with an array rather than a single value in your HTTP request once the form is submitted:
<input type="text" name="relation[]" /><input type="text" name="fullname[]" />
<input type="text" name="relation[]" /><input type="text" name="fullname[]" />
<input type="text" name="relation[]" /><input type="text" name="fullname[]" />
In this example you'd end up with an array relation and an array fullname, both containing your datasets.
I have list of input textbox with class="txtdate"
and
list of another input textbox with class ="txthrs"
like
<div id="dvlitr"><li id="0"><label class="txtdatewrapper"><input type="text" placeholder="Select Date" class="txtdate hasDatepicker" value="" readonly="" id="txtsDate1"><span class="txtdateicon"></span> </label><input type="text" placeholder="Hrs" class="txthours" value="" id="txtsHrs1"><a title="Add" class="btnadd" href="javascript:void(0)"></a><a title="Delete" id="btndelnewli1" class="btndelete" href="javascript:void(0)"></a><input type="hidden" placeholder="Hrs" value="0" id="iDhdn0"></li><li id="2"><label class="txtdatewrapper"><input type="text" placeholder="Select Date" class="txtdate hasDatepicker" readonly="" id="txtsDate2" value="10/28/2013"><span class="txtdateicon"></span></label> <input type="text" placeholder="Hrs" class="txthours" id="txtsHrs2"><a title="Add" class="btnadd" href="javascript:void(0)"></a><a title="Delete" id="btndelnewli2" class="btndelete" href="javascript:void(0)"></a><input type="hidden" placeholder="Hrs" value="0" id="iDhdn2"></li><li id="3"><label class="txtdatewrapper"><input type="text" placeholder="Select Date" class="txtdate hasDatepicker" readonly="" id="txtsDate3" value="10/30/2013"><span class="txtdateicon"></span></label> <input type="text" placeholder="Hrs" class="txthours" id="txtsHrs3"><a title="Add" class="btnadd" href="javascript:void(0)"></a><a title="Delete" id="btndelnewli3" class="btndelete" href="javascript:void(0)"></a><input type="hidden" placeholder="Hrs" value="0" id="iDhdn3"></li></div>
I have to find is there any li with empty date or empty hrs (any one of them)
i tried something like this
var count = $('#dvlitr > li').filter(function() {
var $txtdate = $(this).children('.txtdate').val();
var $txthrs = $(this).children('.txthrs').val();
return (($.trim($txtdate) != '' && $.trim($txthrs) === '') || ($.trim($txtdate) === '' && $.trim($txthrs) != ''))
}).length;
alert(count);
fiddle
but dint get the desired result
please help
Thanks
After looking to your correct HTML markup, you can do this:
var count = $('li').filter(function () {
var $txtdate = $(this).children('.txtdate').val();
var $txthrs = $(this).children('.txthrs').val();
return ($.trim($txtdate) === '' && $.trim($txthrs) === '')
}).length;
console.log('count: ' + count);
Demo: Fiddle
UPDATE
You can do this:
var count = $('li').filter(function () {
var $txtdate = $(this).find('.txtdate').val();
var $txthrs = $(this).find('.txthrs').val();
return ($.trim($txtdate) === '' && $.trim($txthrs) === '')
}).length;
Remove ":" before "input"... will be better but didn't do the trick!
See Jquery selectors doc for more informations
And you make some mistakes. I thing this one should be correct now :
var count = $("li > input.txtdate[value='']+
input.txthrs[value='']").length;
You want to retrieve only the number of li where both of txtdate and txthrs are empty?
You can see it in action in this updated palash fiddle
To complete, in the case that empty value are in fact when value attrib missing, use this one :
var count = $("li > input.txtdate:not([value])+
input.txthrs:not([value])").length;
To be sure to have value attrib : $('input[type=text']:not([value])).attr('value','');
To retrieve the number of line with almost one of field empty, use this :
var count = $.unique($("li").has("input.txtdate[value=],input.txtdate:not([value]),input.txthrs[value=],input.txthrs:not([value])")).length;
Update
$('input:not([value])').attr('value', '');
//Getting only lines when one of input is empty
$.unique($("li").has("input.txtdate[value=''],input.txthrs[value='']")).length;
//Getting only lines when both inputs are empty
$("li > input.txtdate[value='']+input.txthrs[value='']").length;
Trust me, or not...
UPDATE with new DOM input aren't direct child of li
$('input:not([value])').attr('value', '');
//Getting only lines when one of input is empty
$.unique($("li").has("input.txtdate[value=''],input.txthrs[value='']")).length;
//Getting only lines when both inputs are empty
$("li input.txtdate[value='']+input.txthrs[value='']").length;
I wake up... sorry! So you was right form the beginning, with simple selector, you can't detect a user update of a input! Sorry I was wrong!
I founded this plugin here :
jQuery.extend(
jQuery.expr[':'],
{
/// check that a field's value property has a particular value
'field-value': function (el, indx, args) {
var a, v = $(el).val();
if ( (a = args[3]) ) {
switch ( a.charAt(0) ) {
/// begins with
case '^':
return v.substring(0,a.length-1) == a.substring(1,a.length);
break;
/// ends with
case '$':
return v.substr(v.length-a.length-1,v.length) ==
a.substring(1,a.length);
break;
/// contains
case '*': return v.indexOf(a.substring(1,a.length)) != -1; break;
/// equals
case '=': return v == a.substring(1,a.length); break;
/// not equals
case '!': return v != a.substring(1,a.length); break;
/// equals
default: return v == a; break;
}
}
else {
return !!v;
}
}
}
);
Wich allow you to make some selecotr like this :
$('input:field-value(=)');//for empty
$.unique($("li").has("input.txtdate:field-value(=),input.txthrs:field-value(=)")).length;
try this. It will retrieve all the inputs and iterate through them.
var count = 0;
$('.txtdate, .txthrs').each(function (){
if($(this).val() == '') { //If inspects if the input has an empty value
//Do something here if the value is empty
//can add css to change the color or
//assign class to it or call method, etc
count++;
}
});
console.log('count: ' + count);