How to access rows from multiple tbody in a table? - c#

I have something like this:
<table id="tableId" runat="server">
<thead>
<tr>
<th>Example thead1</th>
<th>Example thead2</th>
</tr>
</thead>
<tbody id="tb0">
<tr>
<td>Example 1</td>
<td>Something</td>
</tr>
</tbody>
<tbody id="tb1">
<tr>
<td>Example 2</td>
<td>Something</td></tr>
</tbody>
<tbody id="tb2">
<tr>
<td>Example 3</td>
<td>Something</td>
</tr>
</tbody>
</table>
I use multiple "tbody" with different IDs so I can delete it or create it anytime I want.
What I'd like to do is getting each row from multiple "tbody" from the table "tableId".
In C#, if I use the command "tableId.Rows[0].Cells[0].InnerHtml", I get the result: "Example thead1".
But if I use "tableId.Rows[3].Cells[0].InnerHtml", I can't get the "Example 3" as available in table row, instead of it I get an error which says that row doesn't exist or it's out of index.

I have tested the your code. and found that tableId.Rows[3].Cells[0].InnerHtml will give always Example 3 ..that's is correct as per your aspx markup code.
i think you have place row index incorrect i.e. something like below
tableId.Rows[4].Cells[0].InnerHtml which not exist in your table structure.
that's why you get error of Specified argument was out of the range of valid values.Parameter name: index
Hope this will helps you...happy coding...

Make the tbody elements server controls.
<tbody id="tb2" runat="server">
<tr>
<td>Example 3</td>
<td>Something</td>
</tr>
</tbody>
Then toggle the visibility as you need:
tb2.Visible = true / false;
As per comment:
Probably the designer doesn't create a reference to tb1, tb2, tb3 controls because they are inner controls of tableId.
If so:
var tb2 = tableId.FindControl("tb2");
tb2.Visible = ...

You could use JQuery, something like this....
$('tbody', '#tableid');
But I guess depends on what you want to do with them, and where
JQuery docs:
Selectors: http://api.jquery.com/category/selectors/
Traversing: http://api.jquery.com/category/traversing/

Related

How to Retrieve Specific table from HTML FILE in c#?

I have an HTML file that contains many tables, but I want to access a specific table from the file (not all tables).
So how can I do that?
Code is look something like below and all tables are without ids
`<table border=1>
<tr><td>VI not loadable</td><td>0</td></tr>
<tr><td>Test not loadable</td><td>0</td></tr>
<tr><td>Test not runnable</td><td>0</td></tr>
<tr><td>Test error out</td><td>0</td></tr>
</table>`
every table should have an Id or something that could be Identified from the others, if so you can get it via jquery. for example :
<table class="table table-striped" id="tbl1">
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr>
<td>John</td>
<td>Doe</td>
<td>john#example.com</td>
</tr>
<tr>
<td>Mary</td>
<td>Moe</td>
<td>mary#example.com</td>
</tr>
<tr>
<td>July</td>
<td>Dooley</td>
<td>july#example.com</td>
</tr>
</tbody>
and get it like this:
var table = $('#tbl1').html();
if not you can find it by its priority in the file. for example you can access to 2nd table like this :
var table = $('table:nth-child(2)')
or in C# maybe this would help:
HtmlNode table = doc.DocumentNode.SelectSingleNode("//table[1]")
foreach (var cell in table.SelectNodes(".//tr/td"))
{
string someVariable = cell.InnerText
}

.NET MVC view returning object name rather than specific object

I'm returning a list of lists to an MVC view, displayed as a set of tables. I'm using nested for loops to try to return the values within each list.
The tables are returning to the view with the expected number of rows in each, but instead of values in each of the rows, they are returning the names of their parameters, rather than the actual value of each parameter.
The code in my view is currently:
#if (Model.Report != null)
{
for(var row = 0; row < Model.Report.Count(); row++)
{
<table>
<tr>
<td><h4>#Html.DisplayNameFor(m=>m.Report[row].ReportData[row].ReportingGroup)</h4></td>
</tr>
<tr class="table-header">
<td style="text-align: center">Name</td>
<td style="text-align: center">Area</td>
<td style="text-align: center">Message</td>
</tr>
#for (var listing = 0; listing < Model.Report[row].ReportData.Count(); listing++)
{
<tr>
<td style="text-align: center">#Html.LabelFor(a=>a.Report[row].ReportData[listing].Name)</td>
<td style="text-align: center">#Html.LabelFor(a => a.Report[row].ReportData[listing].Area)</td>
<td style="text-align: center">#Html.LabelFor(a => a.Report[row].ReportData[listing].Message)</td>
</tr>
}
</table>
}
}
Rather than the actual values of the parameters returning to each row in the view, I'm returning the parameter names("Name", "Area", and "Message") to each. I am wondering where I've gone wrong here.
LabelFor displays a label for the property you pass it. Without further attributes, that's just the property name.
You just want to print the value itself:
#Model.Report[row].ReportData[listing].Message
You should replace your for loop with a foreach so you don't need to do all that.
Change your LabelFor to DisplayFor or TextBoxFor in your for loop.

Selecting all textnodes in table with XPath

This is a a page from an open databse about food:
http://www.dabas.com/ProductSheet/Details.ashx/121308
Im trying to get some info from this page using XPath.
The table I'm interested in is the one called: Näringsvärde.
I want to get all the textnodes inside "Näringsvärde" saved into a string.
This is the relevant portion of the code linked above:
<!DOCTYPE html>
<html>
...
<body>
...
<table class="width100" style="page-break-inside: avoid">
<caption>
Produktinformation
<img src="../../images/ProductSheet/draw-triangle3.png" id="toggleProduktinformation"
class="imgCaptionOn" />
</caption>
<tbody id="tbodyProduktinformation">
<tr>
<td class="col1">
Ursprungsland:
</td>
<td>
Sverige </td>
</tr>
...
</tbody>
</table>
<table id="tableHover" class="width100 marginTop30 bgTable">
<tr class="nohover">
<td class="tdLeft48 padding0">
<table id="nutritiveTabel" class="leftTable" style="page-break-inside: avoid">
<caption>
Näringsvärde
<img src="../../images/ProductSheet/draw-triangle3.png" id="toggleNutritiveValues"
class="imgCaptionOn" />
</caption>
<tbody id="tbodyNutritiveValues">
<tr id="divNutritiveValues">
<td class="padding">
<table class="noBorder width100">
<tr>
<td class="col1">
Tillagningsstatus:
</td>
<td>Tillagad</td>
<td colspan="2">
&amp;nbsp;
</td>
</tr>
...
</table>
</td>
</tr>
</tbody>
</table>
</td>
...
</html>
I tried using something like this so far, but it didn't work:
public List<string> GetNaring(string xid) {
HtmlWeb web = new HtmlWeb();
HtmlDocument doc = web.Load(xid);
var xpath = "/html/body/div/div[2]/div[2]/table[2]/tbody/tr/td/table/tbody";
var links = doc.DocumentNode.SelectNodes(xpath);
return links.Select(n => n.InnerText).ToList();
}
But this only gives back null, what am I missing?
The XPath expression:
/html/body/div/div[2]/div[2]/table[2]/tbody/tr/td/table/tbody
does not match any nodes.
Since you have an unique string you can match, you should use it. Searching for that string in the source code, you will find:
...
<td class="tdLeft48 padding0">
<table id="nutritiveTabel" class="leftTable" style="page-break-inside: avoid">
<caption>
Näringsvärde
<img src="../../images/ProductSheet/draw-triangle3.png" id="toggleNutritiveValues"
class="imgCaptionOn" />
</caption>
<tbody id="tbodyNutritiveValues">
<tr id="divNutritiveValues">
...
The string is a child of the caption element inside the table you want. You have to get the string value of that element, trim the extra spaces and use the result to compare to "Näringsvärde". You can select the correct table using this expression:
//table[normalize-space(caption/text())='Näringsvärde']
Once you have the correct table, you can navigate inside it and select the nodes you want, or you can get the string-value which is a concatenation of all the descendant text nodes:
//table[normalize-space(caption/text())='Näringsvärde']//td
This will return all td nodes, which is where the text is.

How to Get All HtmlInputText Controls Within a Table Row?

I'm trying to create a web page to create small playlists. Once data has been entered into the fields, it needs to be saved to an XML file. Currently the table looks like this:
<%-- song list table --%>
<table runat="server" id="table" class="table">
<%-- info row --%>
<thead>
<tr>
<td>Song Title</td>
<td>Song Artist</td>
<td>Song Album</td>
<td><%-- column for delete button --%></td>
</tr>
</thead>
<%-- input rows --%>
<tbody>
<tr>
<td><input runat="server" placeholder="Title" type="text" /></td>
<td><input runat="server" placeholder="Artist" type="text" /></td>
<td><input runat="server" placeholder="Album" type="text" /></td>
<td>
<a href="#">
<img src="Images/Delete.png" onmouseover="this.src='Images/Delete-Hover.png'" onmouseout="this.src='Images/Delete.png'" alt="Delete" />
</a>
</td>
</tr>
</tbody>
</table>
New rows will be added dynamically with jQuery. When the user clicks save, I need to write the table data into their specific XML file. Currently my backend code looks like this:
//for each row
foreach (HtmlTableRow row in table.Rows)
{
//create row info
textWriter.WriteStartElement("Row");
//for each cell
foreach (HtmlTableCell element in row.Cells)
{
//get inputs
//write current input to xml
}
//close row
textWriter.WriteEndElement();
}
My question is where I go from there with my code to be able to get the values of each input and write them to the XML.
You need to give the element's an ID so you can refer to them by. Also, any dynamically added rows will not be able to be accessed this way; that is because they do not exist in the control tree as a server control, but are a pure client control. You would have to access these using Request.Form collection. You'd have to add them dynamically to the control tree if you want them to persist across postbacks too.
If you are using JQuery, it would be more efficient and easier to grab all the values on the client and send the values to a web service or something like that.
My suggestion would be to re-think how you're gathering the data. I assume that you're going to have this information do an HTTP POST to your server using $.ajax() or something similar - and on the server-side, you're wanting to get all of the instances of the Title, Artist and Album fields, grouped by row.
Instead of posting back the table, which is a set of UI elements that display your data, but do not represent it, consider posting back to the server and having the server expect an IEnumerable of Song objects, which would look something like this:
public class Song {
public String Album { get; set; }
public String Artist { get; set; }
public String Title { get; set; }
}
Now, when you bind the form itself, you can bind something like:
<table>
<thead>
<tr>
<td>Song Title</td>
<td>Song Artist</td>
<td>Song Album</td>
<td><%-- column for delete button --%></td>
</tr>
</thead>
<tbody>
<tr>
<td><input placeholder="Title" type="text" name="Songs[0].Title" /></td>
<td><input placeholder="Title" type="text" name="Songs[0].Artist" /></td>
<td><input placeholder="Title" type="text" name="Songs[0].Album" /></td>
</tr>
</tbody>
</table>
The [0] notation indicates that this element is part of an IEnumerable called Songs, and is at index 0. When your jQuery script then goes and adds new rows, you simply increment the indexes. So - a new row would be something like:
<tr>
<td><input placeholder="Title" type="text" name="Songs[1].Title" /></td>
<td><input placeholder="Title" type="text" name="Songs[1].Artist" /></td>
<td><input placeholder="Title" type="text" name="Songs[1].Album" /></td>
</tr>
The only trick to this is to ensure that you never have gaps in your indexes. I.E. - if you have 5 rows, and you delete the third, you need to re-index rows 4 and 5 (by decrementing the [#] values).
Note: All of the above assumes you are using server-side binding.
If you are already using jQuery, you might also find it simpler to simply parse your table's input elements with jQuery and post things as an object that you have direct control over. This prevents you from having to do any indexing at all. An example would be something like:
$('#submit-button').on('click', function (ev) {
var songs = [];
$('#table > tbody > tr').each(function (index, element) {
var $tr = $(element);
var album = $tr.find('input[placeholder=Album]').val();
var artist = $tr.find('input[placeholder=Artist]').val();
var title = $tr.find('input[placeholder=title]').val();
songs.push({ Album: album, Artist: artist, Title: title });
});
$.ajax({
url: '/my/post/url',
type: 'POST',
data: songs
});
});
On the server-side, you will now receive an HTTP POST to /my/post/url which has a payload containing the song data in the table - without having to worry about funky data-binding syntax or indexing.
Hope this helps.

How do I select the NEXT <td> element?

I'm trying to parse some HTML using the HTML Agility Pack. The following code snippet selects the table element containing the information I need but I need to dig deeper into the table.
Once I have the InnerHtml of the table, I plan to look for a <td> with an innertext value of "Field #2", for example. But, then, I need to select the innertext of the NEXT <td>. I need the value 110, in this example. How do I do that?
foreach (var x in doc.DocumentNode.SelectNodes("//table[contains(#class,'data')]"))
{
// psuedo code - search for td and use "contains" on the inner text / html.
// Then, grab the next td inner html.
Console.WriteLine(x.InnerHtml);
}
<tr>
<td width="158"><strong>Field #1:</strong></td>
<td width="99">1</td>
<td width="119"><strong>Field #2:</strong></td>
<td width="176">110</td>
</tr>
<tr>
<td width="158"><strong>Field #3:</strong></td>
<td width="99">85</td>
<td width="119"><strong>Field #4:</strong></td>
<td width="176">-259.34</td>
</tr>
<tr>
<td width="158"><strong>Field #5:</strong></td>
<td width="99">1</td>
<td width="119"><strong>Field #6:</strong></td>
<td width="176">110</td>
</tr>
<tr>
<td width="158"><strong>Field #7:</strong></td>
<td width="99">12</td>
<td width="119"><strong>Field #8:</strong></td>
<td width="176">123.23</td>
</tr>
This piece of code will return you desired td row.
//<td width="176">110</td>
var td = x.SelectNodes("//td").SkipWhile(g => !g.InnerText.Contains("Field #2:")).Select(s => s).Skip(1).FirstOrDefault();
Not sure the agility pack supports it, but in XPath you can query for the next sibling by using /following-sibling:
doc.DocumentNode.SelectNodes(
"//table[contains(#class,'data')]/tr/" +
"td[/strong/text()='Field #2:']" +
"/following-sibling:td");
essentially - find all of the td nodes with the given text, and give me it's next sibling td node.

Categories