Dynamic Gridview Template and Unique Control (ie textbox, label) IDs? - asp.net

When creating a Gridview at design time you can create a template column like this:
<asp:TemplateField>
<ItemTemplate>
<asp:Label runat="server" ID="Label1"></asp:Label>
</ItemTemplate>
</asp:TemplateField>
And in the HTML it will give it a unique name like:
<span id="gvSelect_ctl02_Label1">blahblah</span>
And I can then reference this label in the code behind by:
CType(e.Row.FindControl("Label1"), Label)
Which is PERFECT. But I can't figure out how to do this when I'm creating TemplateFields Dynamically. I've got the following code in my "InstantiateIn":
Dim hl As New HiddenField
hl.ID = "hHidden"
hl.Value = 0
AddHandler hl.DataBinding, AddressOf Me.hl_DataBinding
container.Controls.Add(hl)
And this DOES create a hidden control with the ID as hHidden in each row. But it doesn't give it the unique ID like "gvSelect_ctl02_hHidden" it's just "hHidden". And I know there are ways to append the row number to it myself. But I was wondering if there was a way for it to do this automatically. And still allowing me to reference the hiddenfield like:
CType(e.Row.FindControl("hHidden"), HiddenField)

Ugh.. another answer to my own question. I was looking for the name in the RowCreated. I should have been looking for it in the RowDataBound event.
it works now.. now that I'm doing it correctly.
(I may have too many things on the go at once..) :S

Related

Why am I losing a DataItemTemplate control in a column when I remove all other columns in an ASPxGridView?

I have a 1 column grid. During user interaction, more columns are built and added dynamically. No matter how many columns are added or removed from the grid, the initial Print button column should always remain. Here's the grid.
<dx:ASPxGridView ID="gvAdvancedResults"
ClientInstanceName="gvAdvancedResults" runat="server" EnableRowsCache="False" KeyFieldName="ID" AutoGenerateColumns="True">
<Columns>
<dx:GridViewDataColumn Width="3%" VisibleIndex="0" Caption="Print">
<DataItemTemplate>
<dx:ASPxButton ID="btnDisplayMEC" runat="server" OnClick="btnDisplayMEC_OnClick" ClientInstanceName="btnDisplayMEC" CommandArgument='<%# Eval("ID") %>'
CssClass="reportbutt" BackColor="Transparent" Border-BorderStyle="None" Width="16" Height="16">
<Image Url="~/Assets/Images/printer.png" Width="16" Height="16"></Image>
</dx:ASPxButton>
</DataItemTemplate>
</dx:GridViewDataColumn>
</Columns>
</dx:ASPxGridView>
On a button click, I use the following to clear and repopulate the grid columns from datatable dt
'Remove all columns except the first (index 0)
For gix As Integer = gvAdvancedResults.Columns.Count - 1 To 1 Step -1
If gix <> 0 Then gvAdvancedResults.Columns.RemoveAt(gix)
Next
'Add new columns to the gridview
For Each col As DataColumn In dt.Columns
Dim xcol As Object = gvAdvancedResults.Columns(col.ColumnName)
If xcol IsNot Nothing Then Continue For
Dim newcol As New GridViewDataColumn(col.ColumnName, col.ColumnName)
gvAdvancedResults.Columns.Add(newcol)
Next
gvAdvancedResults.DataSource = dt
gvAdvancedResults.DataBind()
Now here's the problem. The "Print" column remains through the column removal process (as it should), but the ASPxButton inside the column's DataItemTemplate disappears. All the columns that are supposed to be added, are added.
Why am I losing my print button but keeping its column?
To answer your original question, it is probably due to having AutoGenerateColumns set to True. When you bind a dataSource it probably auto generates the columns at that point. You can test this by removing your code that adds the columns and I bet you will still see the columns that were in the dataSource. It might be as simple as setting AutoGenerateColumns to False.
For a few alternative ways of doing this if that doesn't work...
It would be a bit of work but you could try making the button column template in the code behind so that you can just clear and recreate all of the columns during each page load.
Or an easier method would be to use a custom command column like so...
This is c# code but it should be similar in VB:
gvAdvancedResults.Columns.Clear();
GridViewCommandColumn commandCol = new GridViewCommandColumn(" ");//Blank for no caption.
commandCol.Name = "command";
commandCol.Width = Unit.Pixel(30);
commandCol.ButtonType = GridViewCommandButtonType.Image;
gvAdvancedResults.Columns.Add(commandCol);
commandCol.VisibleIndex = 0;
GridViewCommandColumnCustomButton btnPrint = new GridViewCommandColumnCustomButton();
btnPrint.ID = "print";
btnPrint.Image.Url = "~/Assets/Images/printer.png";
btnPrint.Image.ToolTip = "Print";
commandCol.CustomButtons.Add(btnPrint );
//Then add the rest of the columns.
Then add a clientSide CustomButtonClick event to the gridView to handle the actual printing via the CustomCallback event:
<dx:ASPxGridView ID="gvAdvancedResults"
ClientInstanceName="gvAdvancedResults" runat="server"
EnableRowsCache="False"
KeyFieldName="ID"
AutoGenerateColumns="False"
OnCustomCallback="gvAdvancedResults_CustomCallback"
OR
OnCustomButtonClick="gvAdvancedResults_CustomButtonClick"
>
<ClientSideEvents CustomButtonClick="function(s, e) {
if (e.buttonID == 'print')
{
//Trigger a callbackPanel that is the parent to the gridView.
someCallbackPanel.PerformCallback(gvAdvancedResults.GetRowKey(e.visibleIndex));
//Or trigger a callback on the gridView and use GetRowKey on the server side.
gvAdvancedResults.PerformCallback('print');//Handle it on the server.
//Or some other way.
}
}" />
Or handle the server side ASPxClientGridView.CustomButtonClick Event. No example unless you really need it.
Notice that I changed AutoGenerateColumns to false since you are manually creating the columns.
Maybe if you make two gridviews, one that contain only the button column, and the other one for the rest of the data, that would surely solve the problem as you won't touch the first gridview and only bind your datas to the second gridview. Just a workaround... Not really an easy solution because you already coded the rest.

How to insert arbitrary number of links in a gridview cell?

I'm populating a gridview using a table and there is column which holds link IDs.
There can by many link IDs in the same cell.
If the cell contains one link ID then it works fine. The challenge is to support arbitrary number of links on the same cell.
My code:
<asp:HyperLinkField DataNavigateUrlFields="CWEID"
DataNavigateUrlFormatString="https://cwe.mitre.org/data/definitions/{0}.html"
DataTextField="CWEID" HeaderText="CWE ID" Target="_blank"/>
The cell value retrieved from table can be: [770\n838\n120], thus the cell should show 3 links for each ID.
I searched around, and it seems we can do it statically but not for arbitrary number of links.
Any pointers on how to accomplish this?
My suggestion is to replace HyperLinkField with TemplateField and nested Repeater inside.
There is example :
<asp:TemplateField HeaderText="CWE ID">
<ItemTemplate>
<asp:Repeater runat="server" ID="rptLinks" DataSource='<%# Eval("CWEID").ToString.Replace("\n", "").Split(" ")%>'>
<ItemTemplate>
<%# Container.DataItem%>
<br />
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:TemplateField>
Repeater will split CWEID (with \n, I think that is vbCrLf in vb.net so You can replace \n with vbCrLf, without double quotes) record and place all links in separated <a href.... You'll get all links in that cell. There is no matter how many links are stored into CWEID (one or more).
So, result will be (for example) :
770<br>
838<br>
120<br>
Ot just one link, depend how many links You have in CWEID.
UPDATE :
Code changed for DataSource to <%# Eval("CWEID").ToString.Replace("\n", " ").Split(" ")%>
You could add a handler to the gridview's RowDataBound event. In that event you have the ability to modify the contents of any of the cells being rendered in the current row of the gridview, including the ability to create new HTML elements and insert them. In the code below I'm adding an icon to the first cell in a gridview if the current row has child data to be shown.
'New Bootstrap style toggle button for details view
Dim toggle As New HtmlGenericControl("i")
With toggle
.Attributes("class") = "icon-plus icon-white"
.Attributes("onclick") = "javascript: gvrowtoggle(" & e.Row.RowIndex + (e.Row.RowIndex + 2) & ")"
.Attributes("id") = "toggleBttn"
End With
Then I append the new control to the first cell in the current row:
e.Row.Cells(0).Controls.Add(toggle)

How to get a RadAutoCompleteBox in a RadGrid to display bound value in Edit mode?

I expect that I'm just doing something just a smidge wrong (newly moved to web-dev from winforms, and new to Telerik). I'm updating an app that primarily has a RadGrid displaying GridBoundColumns that display text normally and turn to textboxes when the row is being edited. I'm converting one of these columns to a GridTemplateColumn that uses a RadAutoCompleteBox in the EditItemTemplate. In normal (display?) mode, the text bound to the item displays correctly, but when the row enters edit mode, the AutoCompleteBox is properly bound to its own data source, but doesn't display the grid-row's value for that column. How do I do that?
I have:
<telerik:GridTemplateColumn UniqueName="PartNumber" HeaderText="Part Number" ItemStyle-CssClass="editWidth"
FilterControlAltText="Filter PartNumber column" FilterControlWidth="85%">
<ItemTemplate><%#DataBinder.Eval(Container.DataItem, "PartNumber")%></ItemTemplate>
<EditItemTemplate>
<%#DataBinder.Eval(Container.DataItem, "PartNumber")%>
<telerik:RadAutoCompleteBox runat="server" ID="racbPN" DataSourceID="ItemIdSource" DataTextField="IMA_ItemID"
HighlightFirstMatch="true" InputType="Text" TextSettings-SelectionMode="Single" MaxResultCount="200" MinFilterLength="4"
Delimiter="" DropDownHeight="300px" DropDownWidth="200px">
</telerik:RadAutoCompleteBox>
</EditItemTemplate>
<HeaderStyle Width="190px"></HeaderStyle>
</telerik:GridTemplateColumn>
Scouring the Telerik forums, I've seen some references to putting code in the ItemDataBound event. That code is typically in C# and my converted-to-VB implementations never work. I don't know if I'm mistranslating or they're not really the answer for my situation, but here's an example of something I've tried in the code-behind:
If e.Item.IsInEditMode Then
Dim item As GridEditableItem = e.Item
If Not e.Item Is GetType(IGridInsertItem) Then
Dim auto As RadAutoCompleteBox = CType(item.FindControl("racbPN"), RadAutoCompleteBox)
auto.Entries.Add(New AutoCompleteBoxEntry(item("PartNumber").Text, item("GSIS_AMRKey").Text))
End If
End If
Thanks for taking a look and please let me know what other info I need to provide if I've left something important out.
(Should radautocompletebox be a valid tag?)
Telerik tech support got back to me with an answer. The code I listed above in the OnItemDataBound event was almost right. This works:
If e.Item.IsInEditMode Then
If Not e.Item Is GetType(IGridInsertItem) Then
Dim partNumber As String = DirectCast(e.Item.DataItem, DataRowView)("PartNumber").ToString
Dim auto As RadAutoCompleteBox = DirectCast(e.Item.FindControl("racbPN"), RadAutoCompleteBox)
auto.Entries.Add(New AutoCompleteBoxEntry(partNumber))
End If
End If

Adding an edit button to Gridview

I have written a method that retrieves data from the database and returns a datatable comprising of three columns.
This datatable I am binding to a gridview control after hiding the ID field.
DataTable dt = _qbObj.getAllTags();
dvTags.DataSource = dt;
BoundField bfName = new BoundField();
bfName.DataField = dt.Columns["Name"].ToString();
bfName.HeaderText = "Name";
BoundField bfId = new BoundField();
bfId.DataField = dt.Columns["ID"].ToString();
bfId.Visible = false;
BoundField bfDesc = new BoundField();
bfDesc.DataField = dt.Columns["Description"].ToString();
bfDesc.HeaderText = "Description";
dvTags.Columns.Add(bfId);
dvTags.Columns.Add(bfName);
dvTags.Columns.Add(bfDesc);
dvTags.DataBind();
To this gridview control, I want to add an edit button, which should pop-up a jquery modal dialog box where I can enter the updated details.
I realize that I can go with a , but the problem is that I need to pop that modal dialog box without refreshing the page, and the doesn't exactly have great support for scripting.
So inside my gridview I inserted this, an Item Template.
<asp:GridView ID="dvTags" runat="server" CssClass="labs-grid-view"
AutoGenerateColumns="False" >
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="btnEdit" runat="server" Text="Edit" OnClick="dvTagEdit" CommandName="UpdateRecord"
CommandArgument='<%# Eval("ID") %>' />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Now after I am done editing the gridview doesn't update automatically, and hence I have a dedicated refresh grid button which deletes the existing "dynamically inserted columns" using this code :-
int noOfRows = dvTags.Columns.Count;
if (noOfRows > 1)
{
dvTags.Columns.RemoveAt(noOfRows - 1);
dvTags.Columns.RemoveAt(noOfRows - 2);
dvTags.Columns.RemoveAt(noOfRows - 3);
// THERE ARE A TOTAL OF **THREE** COLUMNS
}
But the problem is that after refreshing the page a couple of times, my button inside the ItemTemplate disappears and in the html is replaced with an " "
Please help me find the error. I'm thinking there is a better and easier way to achieve this. If so I'm open to them.
Thanks for reading,
Abijeet.
A few things to consider:
First, can you post your code behind for your ItemCommand event, which is rendering the modal popup and which is performing the update? It could be that your refresh after edit is not processing properly.
Second, instead of doing your databinding "inline" on your GridView, consider using the RowDataBound event within the Gridview. You can detect which row is being generated (header, data, footer) and you can properly create your edit button in there. Better yet, you can access your button from within this method and simply set the CommandArgument to your Id.
Third, when using the asp button in your GridView, it will trigger the "ItemCommand" event when clicked, which will cause a postback.
I'd recommend having a simple link in your template column, or something that you can use to trigger a jQuery modal, and you can setup a static naming convention for your items that will properly retrieve the data to put into your modal popup for editing. Then from there you should be able to process your update as normal.
I hope something in here helps.

AutoCompleteExtender control in a repeater

I have an AutoCompleteExtender AjaxControlToolkit control inside a repeater and need to get a name and a value from the web service. The auto complete field needs to store the name and there is a hidden field that needs to store the value. When trying to do this outside of a repeater I normally have the event OnClientItemSelected call a javascript function similiar to
function GetItemId(source, eventArgs)
{
document.getElementById('<%= ddItemId.ClientID %>').value = eventArgs.get_value();
}
However since the value needs to be stored in a control in a repeater I need some other way for the javascript function to "get at" the component to store the value.
I've got some JavaScript that might help you. My ASP.Net AutoComplete extender is not in a repeater, but I've modified that code to detect the ID of the TextBox you are going to write the erturned ID to, it should work (but I haven't tested it all the way through to post back).
Use the value from 'source' parameter in the client side ItemSelected method. That is the ID of the calling AutoComplete extender. Just make sure that you assign an ID the hidden TextBox in the Repeater Item that is similar to the ID of the extender.
Something like this:
<asp:Repeater ID="RepeaterCompareItems" runat="server">
<ItemTemplate>
<ajaxToolkit:AutoCompleteExtender runat="server"
ID="ACE_Item"
TargetControlID="ACE_Item_Input"
...other properties...
OnClientItemSelected="ACEUpdate_RepeaterItems" />
<asp:TextBox ID="ACE_Item_Input" runat="server" />
<asp:TextBox ID="ACE_Item_IDValue" runat="server" style="display: none;" />
</ItemTemplate>
</asp:Repeater>
Then the JS method would look like this:
function ACEUpdate_CustomerEmail(source, eventArgs) {
UpdateTextBox = document.getElementById(source.get_id() + '_IDValue');
//alert('debug = ' + UserIDTextBox);
UpdateTextBox.value = eventArgs.get_value();
//alert('customer id = ' + UpdateTextBox.value);
}
There are extra alert method calls that you can uncomment for testing and remove for production. In a simple and incomplete test page, I got IDs that looked like this: RepeaterCompareItems_ctl06_ACE_Item_IDValue (for the text box to store the value) and RepeaterCompareItems_ctl07_ACE_Item (for the AC Extender) - yours may be a little different, but it looks practical.
Good Luck.
If I understand the problem correctly, you should be able to do what you normally do, but instead of embeding the ClientId, use the 'source' argument. That should allow you to get access to the control you want to update.
Since you are using a Repeater I suggest wiring the OnItemDataBound function...
<asp:Repeater id="rptResults" OnItemDataBound="FormatResults" runat="server">
<ItemTemplate>
<asp:PlaceHolder id="phResults" runat="server" />
</ItemTemplate>
</asp:Repeater>
Then in the code behind use something like
`Private Sub FormatResults(ByVal sender As Object, ByVal e As RepeaterItemEventArgs)
Dim dr As DataRow = CType(CType(e.Item.DataItem, DataRowView).Row, DataRow) 'gives you access to all the data being bound to the row ex. dr("ID").ToString
Dim ph As PlaceHolder = CType(e.Item.FindControl("phResults"), PlaceHolder)
' programmatically create AutoCompleteExtender && set properties
' programmatically create button that fires desired JavaScript
' use "ph.Controls.Add(ctrl) to add controls to PlaceHolder
End Sub`
Voila

Resources