Render a string[] as a series of editable controls? - asp.net

I have a string[] containing individual words parsed out of a paragraph of text. I need to display each word in its own cell, and have each cell be assigned a dynamic width based on the word's length. I want to be able to have as many words as possible within the maximum width of each row.
In short, I'm trying to take a given paragraph of text and present it as a series of editable controls in a way which resembles how it might appear as a plain text document, with each word consuming only its required space on each "line".
I first tried using a DataList with RepeatLayout in Table mode and RepeatColumns to a set value of 10, with a Repeater within containing a Label control; this resulted in 10 words per row but each cell with a fixed width.
I've considered using a GridView with a single column which I would cram with as many words (in the form of Label controls) per row as will fit, adding new rows as necessary until the entire paragraph is built.
Could anyone please share an elegant way to do this?

The magic you're looking for is the "contenteditable" attribute. Works in IE, Firefox and Chrome.
I'm not sure what the hell you're doing with this, lmao... but this ought to work:
Your Code Behind:
protected void Page_Load(object sender, EventArgs e)
{
//creating some bogus collection of strings.
string[] parts = { "this", "is", "a", "test", "of", "the", "goofiest", "ui", "ever" };
//bind it to the repeater.
rptr.DataSource = parts;
rptr.DataBind();
//now we'll add them to a JavaScript array we can access client side.
StringBuilder sb = new StringBuilder();
sb.Append("document.stringItems = new Array();");
for (int i = 0; i < parts.Length; i++)
sb.AppendFormat("document.stringItems[{0}] = '{1}';", i, parts[i]);
ScriptManager.RegisterClientScriptBlock(this, GetType(), "someKey", sb.ToString(), true);
}
protected void btnTest_Click(object sender, EventArgs e)
{
//display the result just so we can see it's working.
lblResult.Text = hdnResult.Value;
}
Your .ASPX:
<asp:Repeater ID="rptr" runat="server">
<ItemTemplate>
<span contenteditable="true" style="border: solid 1px;" onkeyup="updateItem(event, <%#Container.ItemIndex%>)">
<%#Container.DataItem%></span>
</ItemTemplate>
</asp:Repeater>
<asp:HiddenField ID="hdnResult" runat="server" />
<asp:Button ID="btnTest" runat="server" Text="Test" OnClick="btnTest_Click" />
<script type="text/javascript" language="javascript">
//<![CDATA[
function updateItem(e, index) {
//get the source element. (magic here for cross browser lameness)
var src;
if(window.event) {
e = window.event;
src = e.srcElement;
}else{
src = e.target;
}
//update our item in our array.
document.stringItems[index] = src.innerHTML;
//update our hidden field.
var s = '';
var space = false;
for (var i = 0; i < document.stringItems.length; i++) {
if (space)
s += ' ';
else
space = true;
s += document.stringItems[i];
}
var hdnResult = document.getElementById('<%=hdnResult.ClientID%>');
hdnResult.value = s;
}
//]]>
</script>
<asp:Label ID="lblResult" runat="server"></asp:Label>
You'll have to add some javascript for the onkeypress too to make sure they're not adding carriage returns or spaces or whatever it is you don't want them putting in... but this is the basic idea.
I hope that helps. Good luck.

I'll start off by saying that, from a user interface perspective, I'm not entirely sure as to why you would need to do this, but if it must be done you'd do something along these lines:
Start off with your string[]
Create a List<List<string>>
Loop through your original array, adding the strings to a new List<string>
Once you've reached your desired total character length, add the List<string> to your List<List<string>> (which contains your lines)
Repeat these steps until you've gone all the way through your original array
Once this is done, you bind the list o' list to a repeater like this below (pseudo code; might be off a bit):
<asp:Repeater id="rpt1" runat="server">
<ItemTemplate>
<div>
<asp:Repeater id="rpt2" runat="server" DataSource='<%# Container.DataItem %>'>
<ItemTemplate>
<asp:TextBox id="txt1" runat="server" Text='<%# Container.DataItem %>' Width='<%# MyWidthAlgorithm(Container.DataItem) %>'
</ItemTemplate>
</asp:Repeater>
</div>
</ItemTemplate>
</asp:Repeater>

Best way I can think of approaching something like this is to simply use a repeater and float everything left.
<asp:Repeater runat="server" id="rptr">
<ItemTemplate>
<span style="float:left; height:22px; line-height:22px;"><%# Eval("Word") %></span>
</ItemTemplate>
</asp:Repeater>

Related

Creating Webform IDs dynamically within loop

New to asp.net web forms and I'm having a problem figuring out how to control variables within the loops in my HTML. For Example
<%for (int j = 0; j < Model.Route.Length; j++) { %>
<div class="row">
<hr />
<h3 class="col-lg-12">Route
<%=j %>
</h3>
<div class="row col-lg-12">
<asp:Label ID="Source1_Route1_ID_Label" runat="server" CssClass="col-lg-5">ID</asp:Label>
<asp:TextBox ID="Source1_Route1_ID_Input" runat="server" CssClass="col-lg-6"></asp:TextBox>
</div>
<div class="row col-lg-12">
<asp:Label ID="Source1_Route1_Input_Label" runat="server" CssClass="col-lg-5">Input</asp:Label>
<asp:TextBox ID="Source1_Route1_Input_Input" runat="server" CssClass="col-lg-6"></asp:TextBox>
</div>
<div class="row col-lg-12">
<asp:Label ID="Source1_Route1_Output_Label" runat="server" CssClass="col-lg-5">Output</asp:Label>
<asp:TextBox ID="Source1_Route1_Output_Input" runat="server" CssClass="col-lg-6"></asp:TextBox>
</div>
</div>
<% } %>
<!-- Start Wrapping Routes Rows -->
The above code looks at an array and makes repeats this section for each instance in the array. The problem is that I need control over each of the instances, but right now if in the code behind section I call Source1_Route1_ID.Input.Text = "TEST" It will change all created instances to "TEST". I wanted to create them like <asp:TextBox ID="Source1_Route<%=j%>_ID_Input> but it's throwing an error saying I can't use <%= %> In my ID. Is there an easier way to solve this problem?
In most cases, you don't need or want a loop.
And in most cases, you don't need to inject/put the code in the markup
So, you want to "repeat" somthing over and over?
And then how do you wire up click events etc.? (huge pain point).
So, in webforms land, unlike PHP etc. that inter-mingle code,and use code to spit out markup?
In the VAST majority of cases, with webforms you don't take that design patter approach. More amazing, in 99% of cases you do not need to.
So, a bit of a "gear" shift is required with webforms, and the result is less effort, less code, and no looping in most cases is required.
So, you want to "repeat" something?
Then use a "repeater" (great name, don't you think???).
So,
Say this markup:
<asp:Repeater ID="repHotels" runat="server" >
<ItemTemplate>
<asp:Label ID="txtHotel" runat="server" Width="200px"
Text='<%# Eval("HotelName") %>' Font-Size="X-Large" >
</asp:Label>
<asp:TextBox ID="txtDescription" runat="server"
Text='<%# Eval("Description") %>'
TextMode="MultiLine" Rows="4" Columns="40" Style="margin-left:20px" >
</asp:TextBox>
<asp:Button ID="cmdView" runat="server" Text="View Hotel" CssClass="btn"
style="margin-left:35px;margin-top:25px"
OnClick="cmdView_Click" CommandArgument=<%# Eval("ID") %>
/>
<div style="border-top: 1px solid black;margin-top:8px;margin-bottom:8px"></div>
</ItemTemplate>
</asp:Repeater>
So, you put the markup you want to repeat inside of the item template.
thus, code to fill is this:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
LoadData();
}
void LoadData()
{
SqlCommand cmdSQL =
new SqlCommand("SELECT * FROM tblHotelsA ORDER BY HotelName");
repHotels.DataSource = MyRstP(cmdSQL);
repHotels.DataBind();
}
public DataTable MyRstP(SqlCommand cmdSQL)
{
DataTable rstData = new DataTable();
using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
{
using (cmdSQL)
{
cmdSQL.Connection = conn;
conn.Open();
rstData.Load(cmdSQL.ExecuteReader());
}
}
return rstData;
}
And now we get/see this:
but, above is NOT really the bonus part.
The button click MOST certainly is the Rossetta stone, since now we have a simple button, but it tied to the row of data.
So this button click code:
protected void cmdView_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
RepeaterItem gRow = (RepeaterItem)btn.NamingContainer;
Debug.Print("row index click = " + gRow.ItemIndex);
Debug.Print("PK passed by button = " + btn.CommandArgument);
Label txtHotel = (Label)gRow.FindControl("txtHotel");
Debug.Print("Value of Hotel = " + txtHotel.Text);
}
Output:
row index click = 1
PK passed by button = 5
Value of Hotel = Inns of Banff
It stands to reason, not "only" would one want to repeat data, but ALSO tie/have/enjoy/use some button click for each row of data.
Note also, do you see any looping??? (nope!!)
Do you see messy code inermixed with the markup? (nope!!!).
So, webforms requires a bit of a different kind of thinking, and design patter approach. Once you get the hang of webforms? You will have sorrowful symphany for how most other framework's function, since as above shows, you don't have to write much code to repeat data, and bonus points is you can simply drop in a button into that repeating data, and get the row information with great ease.

Webforms : Simplest way to allow a user to add/remove textboxes?

I don't want to use a gridview because the rest of the form isn't.
I simply need to be able to create a control to dynamically add/remove textboxes and get the value back either as a list of objects or a comma separated string.. It's proving to be much more difficult than it should.
I tried using Jquery + a regular asp.net textbox, but that only works nicely when they're starting from scratch--prepopulating the DOM with their information becomes a pain.
Is there something painfully simple that I'm missing?
It sounds like you could benefit from creating a CompositeControl.
I recently answered a similar question based on dynamically creating textboxes in which I provided a fairly detailed example.
See: Dynamically add a new text box on button click
Hope this helps.
You can add/remove the input[type=text] elements with jquery, and then use Request.Form in your code behind to get the values by element name.
javascript:
var itemCount = 0;
$("#list .add").click(function(){
itemCount++;
$(this).append("<input type='text' name='item"+itemCount+"'/><button class='remove'>Remove</button>");
});
$("#list .remove").click(function(){
$(this).prev().remove();
});
code behind:
string value1 = Request.Form["item1"];
string value2 = Request.Form["item2"];
There are two ways. The following is made using pure WebForm capabilities. Never do it in the production. It uses too much viewstate and too much updatepanel
this is a code behind
public List<String> ValueContainer {
get {
return (List<String>)ViewState["ValueContainer"];
}
set {
ViewState["ValueContainer"] = value;
}
}
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack) {
ValueContainer = new List<string>();
}
}
private void PopulateRepeater() {
rp1.DataSource = ValueContainer;
rp1.DataBind();
}
protected void lbAdd_Click(object sender, EventArgs e) {
ValueContainer.Add("");
rp1.DataSource = ValueContainer;
rp1.DataBind();
}
protected void rp1_ItemCommand(Object Sender, RepeaterCommandEventArgs e) {
ValueContainer.RemoveAt(e.Item.ItemIndex);
rp1.DataSource = ValueContainer;
rp1.DataBind();
}
Here is the markup
<asp:ScriptManager runat="server" ID="sm1" />
<asp:UpdatePanel runat="server" ID="up1">
<ContentTemplate>
<asp:Repeater runat="server" OnItemCommand="rp1_ItemCommand" ID="rp1">
<ItemTemplate>
<asp:TextBox runat="server" ID="myTextBox" /> <asp:LinkButton Text="Remove" runat="server" ID="lbRemove" />
</ItemTemplate>
</asp:Repeater>
<asp:LinkButton runat="server" ID="lbAdd" onclick="lbAdd_Click" Text="Add" />
</ContentTemplate>
</asp:UpdatePanel>
This is more lightweight version
<asp:HiddenField runat="server" ID="hfMyField" ClientIDMode="Static" />
<script type="text/javascript">
//<![CDATA[
function addTextBox() {
$("#myTextboxesContainer").append($("<input type='text' />").keyup(function () {
var Data = "";
$("#myTextboxesContainer input").each(function () {
Data += $(this).val() + ",";
});
$("#hfMyField").val(Data);
}));
}
//]]>
</script>
<div id="myTextboxesContainer">
</div>
Add textbox
The idea here is doing all dom manipulations using client script and storing everything in a hidden field. When the data is posted back you can retrive the value of the hidden field in a standard way i.e. hfMyField.Value. In this example it is CSV.

Table of label ASP.net

i want to know if i can create an array of labels in the code Behind.
And then, if i can put label from the .aspx in the label Array with the id and manipulate it with this.
Thanks
With a page that looks something like this:
<body>
<form id="form1" runat="server">
<div id="labelSpace" runat="server">
<asp:Label ID="Label1" runat="server" Text="Label" Visible="false" >Label One</asp:Label><br />
<asp:Label ID="Label2" runat="server" Text="Label">Label Two</asp:Label><br />
<asp:Label ID="Label3" runat="server" Text="Label">Label Three</asp:Label><br />
<asp:Label ID="Label4" runat="server" Text="Label" Visible="false">Label Four</asp:Label></div><br />
<asp:Button ID="Button1" runat="server" Text="PostBack and Change Labels" />
</form>
this will get you an array of all of the labels in the <div id="labelSpace"> and none of the other controls:
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)//This will only fire after button is clicked...
{
//This does [just as you ask] provide you with an array of labels
Label[] labels = this.labelSpace.Controls.OfType<Label>().ToArray<Label>();
//& Allows you to manipulate a label in the code behind
// by addressing it's index value;
labels[2].Text = "Modified In Code Behind";
//Something you may find more useful than an array is getting
// a list of labels like:
List<Label> listOfLabels = this.labelSpace.Controls.OfType<Label>().ToList<Label>();
//with a list you can identify an individual label easily like:
IEnumerable<Label> invisibleLabels = listOfLabels.Where(l => l.Visible == false);
//with an 'IEnemerable of invisible labels you can now manipulate those to make them all visible...
foreach (Label l in invisibleLabels)
{
l.Visible = true;
l.Text = "Made Visible";
}
//or, if you just want a single label where the id is "Label2"
var labelThree = labels.Where(p => p.ID == "Label2").First();
labelThree.Text = "selected by label id and then edited";
}
}
If you want to start at the page level you will need to build a recursive routine to drill into child controls to create a masterControlCollection and then call to ".OfType<T>..."
Once you have gotten this far you can use LINQ to select out a particular label or set of labels based on any property you want.
Yes you can. Labels from the .aspx part will have a corresponding object in the codebehind file. There's nothing special about them, they are just like any other .NET object. For example if you have markup that looks like this
<asp:Label runat="server" id="MyLabl1" Text="Some Text 1" />
<asp:Label runat="server" id="MyLabl2" Text="Some Text 2" />
<asp:Label runat="server" id="MyLabl3" Text="Some Text 3" />
<asp:Label runat="server" id="MyLabl4" Text="Some Text 4" />
The codebehind file will have something like
protected global::System.Web.UI.WebControls.Label MyLabl1;
protected global::System.Web.UI.WebControls.Label MyLabl2;
protected global::System.Web.UI.WebControls.Label MyLabl3;
protected global::System.Web.UI.WebControls.Label MyLabl4;
Depending on how your project was set up this may be in either the .cs file or the .designer.cs file. From there there's nothing stopping you from adding them to an array and working with them from there.
For example
var labels = new Label[] { MyLabl1, MyLabl2, MyLabl3, MyLabl4 };
will make an array called labels with the 4 labels in it and then you can do whatever you'd like to them. Then you can do labels[0] to get to the first one, etc. Is this what you had in mind?
Label[] labels = new Label[10];
labels[0] = new Label();
labels[0].Text = "blablabla";
...
labels[9] = new Label();
labels[9].Text = "blablabla";

GridView FindControl returns null when HeaderText is set

I have a GridView...
<asp:GridView EnableViewState="true"
ID="grdResults"
runat="server"
CssClass="resultsGrid"
OnRowDataBound="grdResults_OnRowDataBound"
AutoGenerateColumns="false"
HeaderStyle-CssClass="header"
OnRowCommand="grdResults_OnRowCommand">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Label ID="lblView"
runat="server"
Visible="false"
Text="View">
</asp:Label>
<asp:HyperLink ID="hypEdit"
runat="server"
Visible="false"
Text="(Edit)"
CssClass="edit">
</asp:HyperLink>
<asp:LinkButton ID="btnDelete"
runat="server"
Visible="false"
Text="(Delete)"
CssClass="delete"
CommandName="DeleteItem"
OnClientClick="return confirm('Are you sure you want to delete?')">
</asp:LinkButton>
<asp:HyperLink ID="hypSelect"
runat="server"
Visible="false"
Text="(Select)"
CssClass="select">
</asp:HyperLink>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
This has one static column containing a label two hyperlinks and a link button and also has a number of dynamically generated columns...
private void SetupColumnStructure(IEnumerable<string> columnNames)
{
var columnNumber = 0;
foreach (var columnName in columnNames)
{
var templateColumn = new TemplateField
{
ItemTemplate = new CellTemplate(columnName)
};
grdResults.Columns.Insert(columnNumber, templateColumn);
columnNumber++;
}
}
As part of the OnRowDataBound handler I retrieve one of the controls in the statically column and set some attributes on it...
protected void grdResults_OnRowDataBound(object sender, GridViewRowEventArgs e)
{
.
.
.
var row = e.Row;
var rowData = row.DataItem as Dictionary<string, object>;
if (rowData != null)
{
if ((bool)rowData[displayEditLink])
{
var hypEdit = (HyperLink)row.FindControl("hypEdit");
hypEdit.NavigateUrl = "~/Pages/Edit.aspx?action=Edit&objectType=" + rowData[objectTypeLiteral] + "&id=" + rowData[objectIdLiteral];
hypEdit.Visible = true;
}
}
.
.
.
}
This all works fine but no column names are displayed. So I then modify the SetupColumnStructure method so that the HeaderText is set on the template field like this...
private void SetupColumnStructure(IEnumerable<string> columnNames)
{
var columnNumber = 0;
foreach (var columnName in columnNames)
{
var templateColumn = new TemplateField
{
ItemTemplate = new CellTemplate(columnName),
HeaderText = columnName
};
grdResults.Columns.Insert(columnNumber, templateColumn);
columnNumber++;
}
}
For some reason this one extra line change causes the row.FindControl("hypEdit"); call in the OnRowDataBound handler to return null.Can anyone see something im missing here or has anyone experienced a similar issue?
UPDATE
I've made sure that I'm not referring to a header or footer row here. Also, if I step over the object reference exception this occurs for every item that is in the DataSource.
Not sure if this helps, but as I expected, when I stepped through the code the table has generated all the columns expected but all cells (DataControlFieldCells) contain no controls when the HeaderText is set, yet all expected controls when it isnt set.
All very strange. Let me know if you can spot anything else.
When you added the HeaderText, a new RowType was added to the gridview. You'll need to check what type of row raised the OnRowDataBound event and take the appropriate action. In your case, just checking if the e.Row.RowType is a DataRow should solve your problem:
protected void grdResults_OnRowDataBound(object sender, GridViewRowEventArgs e)
{
if(e.Row.RowType == DataControlRowType.DataRow)
{
if ((bool)rowData[displayEditLink])
{
var hypEdit = (HyperLink)row.FindControl("hypEdit");
hypEdit.NavigateUrl = "~/Pages/Edit.aspx?action=Edit&objectType=" + rowData[objectTypeLiteral] + "&id=" + rowData[objectIdLiteral];
hypEdit.Visible = true;
}
}
}
Its because the control you are searching for is contained within another control. FindControl() does not look inside control collections of controls. You will need to write a recursiveFindControl() method.
Hope this helps a little!

Get GridView selected row DataKey in Javascript

I have GridView which I can select a row. I then have a button above the grid called Edit which the user can click to popup a window and edit the selected row. So the button will have Javascript code behind it along the lines of
function editRecord()
{
var gridView = document.getElementById("<%= GridView.ClientID %>");
var id = // somehow get the id here ???
window.open("edit.aspx?id=" + id);
}
The question is how do I retrieve the selected records ID in javascript?
I worked it out based on JasonS response. What I did was create a hidden field in the Grid View like this:
<asp:TemplateField ShowHeader="False">
<ItemTemplate>
<asp:HiddenField ID="hdID" runat="server" Value='<%# Eval("JobID") %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField Visible="False">
<ItemTemplate>
<asp:LinkButton ID="lnkSelect" runat="server" CommandName="select" Text="Select" />
</ItemTemplate>
</asp:TemplateField>
Then on the OnRowDataBind have code to set the selected row
protected virtual void Grid_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
// Click to highlight row
Control lnkSelect = e.Row.FindControl("lnkSelect");
if (lnkSelect != null)
{
StringBuilder click = new StringBuilder();
click.AppendLine(m_View.Page.ClientScript.GetPostBackClientHyperlink(lnkSelect, String.Empty));
click.AppendLine(String.Format("onGridViewRowSelected('{0}')", e.Row.RowIndex));
e.Row.Attributes.Add("onclick", click.ToString());
}
}
}
And then in the Javascript I have code like this
<script type="text/javascript">
var selectedRowIndex = null;
function onGridViewRowSelected(rowIndex)
{
selectedRowIndex = rowIndex;
}
function editItem()
{
if (selectedRowIndex == null) return;
var gridView = document.getElementById('<%= GridView1.ClientID %>');
var cell = gridView.rows[parseInt(selectedRowIndex)+1].cells[0];
var hidID = cell.childNodes[0];
window.open('JobTypeEdit.aspx?id=' + hidID.value);
}
</script>
Works a treat :-)
1) change your javascript function to use a parameter
function editRecord(clientId)
{ ....
2) output the call in your editRecord button... if you want to avoid dealing with the .net generated ids, just use a simple
<input type="button" onclick="editRecord(your-rows-client-id-goes-here)" />
Based off of your comments to #DaveK's response, in javascript you can set the id of a hidden field to the clientId of the selected row when the user selects it. Then have your editRecord function use the value set on the hidden form field.
one could avoid javascript altogether, by setting anchor tags pre-populated with the query string for each row (although this will effect your table layout, it will need only one click rather than 2 from the user)
insert in the gridview template:
<asp:HyperLink runat="server" ID="editLink" Target="_blank"
NavigateURL='<%# Eval("JobID","edit.aspx?id={0}") %>'>
Edit..
</asp:HyperLink>

Resources