amchart in listview post back - c#

I have a webform app where I list items using listview.
there are an amchart created in each item, and I'm using stringBuilder write the script from code, then inserting it into a div called "chartArea" to load the script in each amchart item:
Literal lit = new Literal();
lit.Text = sb2.ToString();
chartArea.Controls.Add(lit);
the above works great during the first page load, but then when I trigger a postback event, I get an error on amchart file:
Unhandled exception at line 106, column 73 in http://localhost:33154/Scripts/amcharts/amcharts.js
0x80004005 - JavaScript runtime error: Unspecified error.
What Shall I do?
This is my code to load the chart:
public void buildChart(string ACreg, HtmlGenericControl chartArea)
{
Literal lit = new Literal();
lit.Text = "";
chartArea.Controls.Add(lit);
//-------- create chart data table----------
int Rdays = 90;
DataTable engChartData = new DataTable();
engChartData.Columns.Add("date", typeof(string));
engChartData.Columns.Add("eng1Val", typeof(string));
engChartData.Columns.Add("eng2Val", typeof(string));
DateTime initialDate = DateTime.Now.AddDays(-Rdays);
for (int i = 0; i <= Rdays; i++)
{
string dateMe = initialDate.AddDays(i).ToString("yyyy-MM-dd");
engChartData.Rows.Add(dateMe, "0", "0");
}
//--------- bind chart data-----------------
using (APMEntitiesW APMe = new APMEntitiesW())
{
var countDate = APMe.Points.Where(x => x.point_Del == false && x.point_Act == true && x.point_Reg == ACreg).GroupBy(g => new { g.point_DateTime, g.point_EGT1, g.point_EGT2 }).Select(lg => new { date = EntityFunctions.TruncateTime(lg.Key.point_DateTime), eng1Val = lg.Key.point_EGT1, eng2Val = lg.Key.point_EGT2 }).ToList();
foreach (DataRow dr in engChartData.Rows)
{
foreach (var data in countDate)
{
string datFromQ = data.date.Value.ToString("yyyy-MM-dd").Replace(" 12:00:00 AM", "");
string deteFromDT = dr["date"].ToString().Replace(" 12:00:00 AM", "");
if (datFromQ == deteFromDT)
{
dr["eng1Val"] = data.eng1Val.ToString();
dr["eng2Val"] = data.eng2Val.ToString();
break;
}
}
}
StringBuilder sb = new StringBuilder();
foreach (DataRow item in engChartData.Rows)
{
string date = string.Format("{0:MMM DD,YYYY}", item["date"]);
sb.AppendLine(#"{""date"": """ + date + #""",");
sb.AppendLine(#"""Engine1"": """ + item["eng1Val"] + #""",");
sb.AppendLine(#"""Engine2"": """ + item["eng2Val"] + #"""},");
}
//ChartData = sb.ToString();
StringBuilder sb2 = new StringBuilder();
sb2.AppendLine(#"<script> var chart = AmCharts.makeChart(""chart" + ACreg + #""", {");
sb2.AppendLine(#"""type"": ""serial"",""categoryField"": ""date"", ""startEffect"": ""easeOutSine"",""dataDateFormat"": ""DD-MM-YYYY"", ""startDuration"": 1,");
sb2.AppendLine(#"""categoryAxis"": {""gridPosition"": ""start"", ""autoGridCount"": false, ""balloonDateFormat"": ""DD MMM YYYY""},");
sb2.AppendLine(#" ""chartScrollbar"": {""enabled"": true, ""offset"": 40, ""oppositeAxis"": false, ""scrollbarHeight"": 13},");
sb2.AppendLine(#" ""chartCursor"": { ""enabled"": true}, ""trendLines"": [],");
sb2.AppendLine(#"""graphs"": [{""balloonText"": ""[[title]]: [[value]]"", ""bullet"": ""round"", ""id"": ""AmGraph-1"", ""title"": ""Engine1"", ""type"": ""smoothedLine"", ""valueField"": ""Engine1"", ""bullet"": ""bubble"", ""bulletBorderAlpha"": 1, ""bulletSize"": 1,""bulletBorderColor"": ""#FF0000"",""bulletBorderThickness"": 1, ""bulletColor"": ""#000000"",""fillAlphas"": 0.41, },");
sb2.AppendLine(#"{""balloonText"": ""[[title]]: [[value]]"",""bullet"": ""square"", ""id"": ""AmGraph-2"", ""title"": ""Engine2"", ""type"": ""smoothedLine"", ""valueField"": ""Engine2"", ""bullet"": ""bubble"",""bulletBorderAlpha"": 1, ""bulletSize"": 1, ""bulletBorderColor"": ""#0098FF"",""bulletBorderThickness"": 1, ""bulletColor"": ""#0098FF"", ""lineColor"": ""#0098FF"",}],");
sb2.AppendLine(#"""guides"": [], ""valueAxes"": [ { ""id"": ""ValueAxis-1"", ""title"": ""Consumption Unit""} ], ""allLabels"": [], ""balloon"": {}, ""legend"": { ""enabled"": true, ""markerBorderThickness"": 0, ""useGraphSettings"": true},");
sb2.AppendLine(#"""titles"": [ { ""id"": ""Title-1"", ""size"": 15, ""text"": ""Engine Fuel Consumption"" } ], ""dataProvider"": [" + sb.ToString() + " ] }) </script>");
lit.Text = sb2.ToString();
chartArea.Controls.Add(lit);
}
}

Related

How to cnvert userAccountControl to 0 or 1

I currently working with AD and I fetch data from AD and store to file. Since I don't need some status code, I am interested only to check If user account is 512- Enable or 514 - Disable and convert to bool value. Here is my code
public static List<Korisnik> VratiKorisnike()
{
List<Korisnik> lstADUsers = new List<Korisnik>();
string sDomainName = "sasaos";
string DomainPath = "LDAP://" + sDomainName;
string constring = #"Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=DesignSaoOsig1; Integrated Security=True";
string Query = "SELECT * FROM tblZaposleni_AD";
DataTable table = new DataTable();
SqlDataAdapter adapter = new SqlDataAdapter(Query, constring);
adapter.Fill(table);
string txt = "";
string fileLoc = #"C:\output.txt";
foreach (DataRow row in table.Rows)
{
string line = "";
foreach (DataColumn column in table.Columns)
{
line += "," + row[column.ColumnName].ToString();
}
txt += line.Substring(1);
}
using (var sw = new StreamWriter(fileLoc))
{
sw.WriteLine(txt);
}
Console.WriteLine("Ok");
DirectoryEntry searchRoot = new DirectoryEntry(DomainPath);
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.Filter = "(&(objectClass=user)(objectCategory=person))";
search.PropertiesToLoad.Add("samaccountname"); // Username
search.PropertiesToLoad.Add("displayname"); // display name
search.PropertiesToLoad.Add("userAccountControl"); // isEnabled
search.PropertiesToLoad.Add("pwdLastSet"); //passwordExpires
DataTable resultsTable = new DataTable();
resultsTable.Columns.Add("samaccountname");
resultsTable.Columns.Add("displayname");
resultsTable.Columns.Add("Neaktivan");
resultsTable.Columns.Add("dontexpirepassword");
SearchResult result;
SearchResultCollection resultCol = search.FindAll();
if (resultCol != null)
{
for (int counter = 0; counter < resultCol.Count; counter++)
{
string UserNameEmailString = string.Empty;
result = resultCol[counter];
if (result.Properties.Contains("samaccountname")
&& result.Properties.Contains("displayname"))
{
int userAccountControl = Convert.ToInt32(result.Properties["userAccountControl"][0]);
string samAccountName = Convert.ToString(result.Properties["samAccountName"][0]);
int isEnable;
int Dont_Expire_Password;
if (userAccountControl > 0)
{
isEnable = 0;
}
else
{
isEnable = 1;
}
if ((userAccountControl & 65536) != 0)
{
Dont_Expire_Password = 1;
}
else
{
Dont_Expire_Password = 0;
}
Korisnik korisnik = new Korisnik();
korisnik.Username = (result.Properties["samaccountname"][0]).ToString();
korisnik.DisplayName = result.Properties["displayname"][0].ToString();
korisnik.isEnabled = Convert.ToBoolean(result.Properties["userAccountControl"][0]);
DataRow dr = resultsTable.NewRow();
dr["samaccountname"] = korisnik.Username.ToString();
dr["displayname"] = korisnik.DisplayName.ToString();
dr["neaktivan"] = Math.Abs(isEnable);
dr["dontexpirepassword"] = Dont_Expire_Password;
resultsTable.Rows.Add(dr);
lstADUsers.Add(korisnik);
}
}
var json = JsonConvert.SerializeObject(resultCol, Formatting.Indented);
var res = json;
Console.WriteLine("Ispis uspjesno obavljen");
Console.ReadLine();
File.WriteAllText(fileLoc, json);
}
return lstADUsers;
}
Any idea how to resolve this issue I would be very thankfull
So fat what I try
if (userAccountControl == 512)
{
dr["neaktivan"] = "Account Enabled";
}
if (userAccountControl == 514)
{
dr["neaktivan"] = "Account Disabled";
}
Doesn't show in my output.txt any result
{
"Path": "LDAP://sarajevoosigura/CN=Aldin Smajović,OU=Sarajevo,OU=People,DC=sarajevoosiguranje,DC=ba",
"Properties": {
"displayname": [
"John Smith"
],
"useraccountcontrol": [
514
],
"samaccountname": [
"jsmith"
],
"adspath": [
"LDAP://test/CN=JohnSmith,OU=New York,OU=People,DC=sasa,DC=ba"
],
"pwdlastset": [
132295140030347373
]
}
},
Don't think about the numbers 512 or 514, since they aren't actually relevant to what you're trying to find. The userAccountControl attribute is a bit flag, meaning that each bit (0 or 1) in the binary value is a flag that means something (1 is on and 0 is off). The decimal representation of all those bits could be all kinds of values. For example, the decimal value could be 512 or 514 or even 65538 (if the account is disabled and has "don't expire password"). So ignore the decimal value.
The second bit is the flag for "disabled". If the second bit is 1, the account is disabled. That's what you want to find.
John's answer does work in finding that, but it's (slightly) overly complicated.
You're already doing a proper test for the "don't expire password" flag:
if ((userAccountControl & 65536) != 0)
65536 is 10000000000000000 in binary. So that if statement is saying "if the 17th bit is set". You just need to do exactly the same thing for the 2nd bit to figure out if it's disabled:
if (userAccountControl & 2 != 0)
{
isEnable = 1;
}
else
{
isEnable = 0;
}
You can read more about the "Logical AND operator &" here: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators#logical-and-operator-

Epplus linq sub grids in excel

I guess I have been going about this wrong or wording it incorrectly.
I have a Controller with a LINQ query that returns a list and contained within it is a child list for each item. I am using EPPlus to create a excel doc for it and so far it works until I want to add to child list.
What i am looking to do is have a expand option for each row item that expands to the sub list within each item. Below is the code I have so far.
Controller (I chopped this up for the example here so if anything looks off its just because of the hack and job. Results.ToList() was actually a rather long LINQ query.
[HttpGet]
[Route("Download")]
public FileContentResult Download(int? mid)
{
using (var package = new ExcelPackage())
{
byte[] fileContents;
using (var ctx = new dbEntities())
{
results.ToList();
var merchantIdList = (results.GroupBy(t => t.DomainMerchantID, t => t.violations)
.OrderBy(g => g.Key)
.Select(g => g.Key).ToList());
var resultList = new List<ViolationsByMerchant>();
foreach (int? id in merchantIdList)
{
var thisResult = (from r in results
where r.violations.DomainMerchantID == id
orderby r.violations.DateOfInfraction descending
select new ViolationsByMerchant
{
SellerDomain = r.DomainName,
Merchant = r.IdentifierNiceName,
Contact = r.str_Name,
Phone = r.str_Phone,
AccountMatch = "????",
ViolationsProductDetails = from v in results
where v.violations.DomainMerchantID == id
select new ViolationsProductDetails()
{
ProductName = r.memberProducts.ProductName,
ProductLine = r.memberProducts.ProductLine,
Category = r.memberProducts.ProductCategory
}
}).FirstOrDefault();
resultList.Add(thisResult);
}
var worksheet = package.Workbook.Worksheets.Add("Merchant Violations");
int i = 1;
foreach (var item in resultList)
{
worksheet.Cells["A1:Z1"].Style.Font.Size = 15;
worksheet.Cells.AutoFitColumns();
worksheet.Cells["A1:Z1"].Style.Font.Bold = true;
worksheet.Cells[1, 1].Value = "Merchant";
worksheet.Cells["A" + i].Value = item.Merchant;
worksheet.Cells[1, 2].Value = "Account Match";
worksheet.Cells["B" + i].Value = item.AccountMatch;
worksheet.Cells[1, 3].Value = "Seller";
worksheet.Cells["C" + i].Value = item.SellerDomain;
worksheet.Cells[1, 4].Value = "Contact";
worksheet.Cells["D" + i].Value = item.Contact;
worksheet.Cells[1, 5].Value = "Phone";
worksheet.Cells["E" + i].Value = item.Phone;
i++;
}
fileContents = package.GetAsByteArray();
}
return File(
fileContents: fileContents,
contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
fileDownloadName: "MyMappProducts.xlsx"
);
}
}
LINQ Returns something like this if that helps.
{
"merchant": "Foo Merchant,
"accountMatch": "True",
"sellerDomain": "Amazon",
"contact": "John Doe",
"phone": "555-555-5555
"violationsProductDetails": [
{
"productName": "King Kong",
"productLine": "Animal",
"category": "King of the jungle",
},
{
"productName": "Prime",
"productLine": "Transformers",
"category": "Toy",
},
}

The token has already been used, conekta

i have a problem when a make a charge,this show me this error:
"The token has already been used"
"La tarjeta no pudo ser procesada"
when i do this with a test token this works fine but when i do it with another token this don´t work, this is my implementation.
bool band = true;
Order order;
Expression<Func<Usuario, bool>> exp = (x) => x.IdUsuario == IdUsuario;
UsuarioLoader uLoader = new UsuarioLoader();
var usuario = uLoader.GetElementByProperty(exp);
try
{
order = new conekta.Order().create(#"{
""currency"":""MXN"",
""customer_info"": {
""customer_id"": """+usuario.TokenConekta+#"""
},
""line_items"": [{
""name"": ""Cobro Union"",
""unit_price"": 1000,
""quantity"": 1
}],
""charges"": [{
""payment_method"": {
""type"": ""card"",
""token_id"": """+tokenTarjeta+#"""
},""amount"":1000
}]
}");
}
catch (ConektaException e)
{
band = false;
foreach (JObject obj in e.details)
{
System.Console.WriteLine("\n [ERROR]:\n");
System.Console.WriteLine("message:\t" + obj.GetValue("message"));
System.Console.WriteLine("debug:\t" + obj.GetValue("debug_message"));
System.Console.WriteLine("code:\t" + obj.GetValue("code"));
}
}
the problem is that the parameter token_id is for a only one call, but if you want to re-use a card for an automatic payments you must set payment_source_id instead of token_id, this is the correct code:
Expression<Func<Usuario, bool>> exp = (x) => x.IdUsuario == IdUsuario;
UsuarioLoader uLoader = new UsuarioLoader();
var usuario = uLoader.GetElementByProperty(exp);
try
{
order = new conekta.Order().create(#"{
""currency"":""MXN"",
""customer_info"": {
""customer_id"": """+usuario.TokenConekta+ #"""
},
""line_items"": [{
""name"": ""Cobro Union"",
""unit_price"": 1000,
""quantity"": 1
}],
""charges"": [{
""payment_method"": {
""type"": ""card"",
""payment_source_id"": """ + tokenTarjeta+#"""
},""amount"":1000
}]
}");
}
catch (ConektaException e)
{
band = false;
foreach (JObject obj in e.details)
{
System.Console.WriteLine("\n [ERROR]:\n");
System.Console.WriteLine("message:\t" + obj.GetValue("message"));
System.Console.WriteLine("debug:\t" + obj.GetValue("debug_message"));
System.Console.WriteLine("code:\t" + obj.GetValue("code"));
}
}

EXT.NET Dynamically created ComboBox and Store

I am using Ext.Net 1.3 with ASP.NET 4.0
I would like to use C # dynamically generated ComboBox and Store, the following is my code.
var data = new object[]
{
new object[]{"AL", "Alabama", "The Heart of Dixie"},
new object[] { "AK", "Alaska", "The Land of the Midnight Sun"},
new object[] { "AZ", "Arizona", "The Grand Canyon State"},
new object[] { "AR", "Arkansas", "The Natural State"},
new object[] { "CA", "California", "The Golden State"},
new object[] { "CO", "Colorado", "The Mountain State"},
new object[] { "CT", "Connecticut", "The Constitution State"}
};
Ext.Net.ComboBox cmb = new Ext.Net.ComboBox();
cmb.TypeAhead = true;
cmb.ForceSelection = true;
cmb.DisplayField = "ItemCode";
cmb.ValueField = "ItemName";
cmb.MinChars = 1;
cmb.ListWidth = 400;
cmb.PageSize = 10;
cmb.ItemSelector = "tr.list-item";
Store s = new Store();
s.AddField(new RecordField() { Name = "ItemCode", Type = RecordFieldType.String }, 0);
s.AddField(new RecordField() { Name = "ItemName", Type = RecordFieldType.String }, 1);
s.AddField(new RecordField() { Name = "OnHand", Type = RecordFieldType.String }, 2);
s.SaveAllFields = true;
s.DataSource = data;
s.DataBind();
cmb.Store.Add(s);
StringBuilder sHtml = new StringBuilder();
sHtml.Append(" <tpl for=\".\"><tpl if=\"[xindex] == 1\">");
sHtml.Append("<table class=\"cbStates-list\" ><tr>");
sHtml.Append("<th style=\"color: #2f353b !important;\">ItemCode</th>");
sHtml.Append(" <th style=\"color: #2f353b !important;\">ItemName</th>");
sHtml.Append("<th style=\"color: #2f353b !important;\">OnHand</th>");
sHtml.Append("</tr> </tpl>");
sHtml.Append("<tr class=\"list-item\">");
sHtml.Append("<td style=\"padding:3px 0px;\">{ItemCode}</td>");
sHtml.Append("<td>{ItemName}</td>");
sHtml.Append("<td>{OnHand}</td>");
sHtml.Append("</tr> <tpl if=\"[xcount-xindex]==0\">");
sHtml.Append(" </table> </tpl> </tpl>");
cmb.Template.Html = sHtml.ToString();
Panel1.Items.Add(cmb);
If you do not bind the Store, the ComboBox will appear on the page.
If the Store is bound, nothing will be displayed. And the browser gives an error message.
enter image description here
How to solve this problem?
HttpProxy proxy = new HttpProxy
{
Method = HttpMethod.POST,
Url = "../../../Handlers/BoneWL.ashx"
};
// Create Reader
Ext.Net.JsonReader reader = new Ext.Net.JsonReader
{
Root = "plants",
TotalProperty = "total",
Fields = {
new RecordField("ItemCode"),
new RecordField("ItemName"),
new RecordField("OnHand")
}
};
// Add Proxy and Reader to Store
Store store = new Store
{
Proxy = { proxy },
Reader = { reader },
AutoLoad = false
};
// Create ComboBox
Ext.Net.ComboBox cmb = new Ext.Net.ComboBox
{
DisplayField = "ItemCode",
ValueField = "ItemCode",
TypeAhead = false,
LoadingText = "加载中...",
Width = 240,
PageSize = 10,
HideTrigger = true,
ItemSelector = "tr.list-item",
MinChars = 1,
Store = { store }
};
cmb.Listeners.TriggerClick.Handler = "UseDirectEvents('1');WinRowCancelEdit();";
cmb.Triggers.Add(new FieldTrigger() { Icon = TriggerIcon.Search });
cmb.TriggerIcon = TriggerIcon.Search;
StringBuilder sHtml = new StringBuilder();
sHtml.Append(" <tpl for=\".\"><tpl if=\"[xindex] == 1\">");
sHtml.Append("<table class=\"cbStates-list\" ><tr>");
sHtml.Append("<th style=\"color: #2f353b !important;\">ItemCode</th>");
sHtml.Append(" <th style=\"color: #2f353b !important;\">ItemName</th>");
sHtml.Append("<th style=\"color: #2f353b !important;\">OnHand</th>");
sHtml.Append("</tr> </tpl>");
sHtml.Append("<tr class=\"list-item\">");
sHtml.Append("<td style=\"padding:3px 0px;\">{ItemCode}</td>");
sHtml.Append("<td>{ItemName}</td>");
sHtml.Append("<td>{OnHand}</td>");
sHtml.Append("</tr> <tpl if=\"[xcount-xindex]==0\">");
sHtml.Append(" </table> </tpl> </tpl>");
cmb.Template.Html = sHtml.ToString();
Panel1.Items.Add(cmb);
Example.portal

passing json values to highcharts from .net code behind

var Javascriptxvalue= $.parseJSON($("#hdnXaxis").val());
var Javascriptyvalue= $.parseJSON($("#hdnYaxis").val());
$(document).ready(DrawMyGraph1);
function DrawMyGraph1() {
chart = new Highcharts.Chart(
{
chart: {
type: 'column',
renderTo: 'container3',
defaultSeriesType: 'area'
},
title: {
text: ''
},
subtitle: {
text: ''
},
xAxis: {
categories: Javascriptxvalue,
labels: {
enabled: false
}
},
yAxis: {
title: {
text: 'No of Patients'
}
},
credits: {
enabled: false
},
tooltip: {
formatter: function () {
return this.series.name + ' - ' + Highcharts.numberFormat(this.y, 0);
}
},
series: Javascriptyvalue
});
}
c# code
void FastMovingStocksBarChart(string date1, string date2, string selperiod, string sql)
{
DataSet dschart = new DataSet();
dschart = _obj_MIS.DoctorpatientreportChart(date1, date2, selperiod,sql);
List lstXaxis = new List();
List lstcolors = new List();
lstcolors.Add("#3366DD");
//lstcolors.Add("#FFEE22");
//lstcolors.Add("#33BBCC");
lstcolors.Add("#CC0022");
//lstcolors.Add("#FF0000");
lstcolors.Add("#339900");
lstcolors.Add("#FF7700");
lstcolors.Add("#33BBCC");
lstcolors.Add("#99EEEE");
lstcolors.Add("#6699FF");
lstcolors.Add("#9966BB");
lstcolors.Add("#99BB66");
lstcolors.Add("#FF7700");
lstcolors.Add("#FFEE22");
lstcolors.Add("#FFCBB9");
lstcolors.Add("EAEC93");
lstcolors.Add("D7FBE6");
lstcolors.Add("FFCACA");
for (int i = 0; i < dschart.Tables[0].Rows.Count; i++)
{
lstXaxis.Add(dschart.Tables[0].Rows[i]["Doctor Name"].ToString());
}
List<ChartEx> lstseries = new List<ChartEx>();
int count = 0;
for (int i = 0; i < dschart.Tables[0].Rows.Count; i++)
{
ChartEx oEx = new ChartEx();
oEx.name = dschart.Tables[0].Rows[i]["Doctor Name"].ToString();
//oEx.data.Add(Convert.ToInt32(dschart.Tables[0].Rows[i]["Patients"]));
oEx.data = new List<int>() { Convert.ToInt32(dschart.Tables[0].Rows[i]["Patients"]) };
oEx.color = lstcolors[count];
lstseries.Add(oEx);
count++;
if (count >= lstcolors.Count)
count = 0;
}
//Convert X axis data to JSON
JavaScriptSerializer oSerializer1 = new JavaScriptSerializer();
hdnXaxis.Value = oSerializer1.Serialize(lstXaxis);
//Convert Y axis data to JSON
JavaScriptSerializer oSerializer2 = new JavaScriptSerializer();
hdnYaxis.Value = oSerializer1.Serialize(lstseries);
}
I am not getting the values for "Javascriptxvalue" and "Javascriptyvalue" inside the chart function
can anyone help me
Regards
Prabhu
Presumably, 'hdnXaxis' is the id of a HiddenFieldControl server control? Perhaps the id is not what you think
var Javascriptxvalue= $.parseJSON($("#"+ <%= hdnXaxis.ClientId %>).val());
Instead of passing strings via an input, you could use server tags to directly inject the values into the page. Like this:
<%= "alert('" + MyPublicProperty + "')" %>
This should alert you to the value of the property defined in your code behind. You could then set it to a js variable like so:
<%= "var Javascriptxvalue = '" + xProperty + "';" %>
You will need to run this bit of code directly in an aspx/ascx/razor page to set the variables though, I think it's better than relying on a control with a particular id though.

Categories