I'm adding some content to a given web page from code behind. When I want to add a break after some text, I try to do that this way:
pDoc.Controls.Add(New Label With {.Text = "whatever"})
pDoc.Controls.Add(New HtmlGenericControl("br"))
,where pDoc is the Panel in which I'm adding the content. But it adds two br tags into the final HTML.
I've avoid this behaviour this way:
pDoc.Controls.Add(New Label With {.Text = "whatever" & "<br />"})
Anyway, I'm so curious and I want to know why
pDoc.Controls.Add(New HtmlGenericControl("br"))
is acting that way. I also think my approach is not too fancy.
Regards,
Actually you can use;
pDoc.Controls.Add(new LiteralControl("<br/>"));
Whereas new HtmlGenericControl("br") adds two <br>, this will only add <br/> tag to your HTML so that you just have 1 space line.
In this picture I added those breaks with that code block.
Also similar question here: Server control behaving oddly
After some testing it looks like the reason is that HtmlGenericControl doesn't support self closing. On server side the HtmlGenericControl("br") is treated as:
<br runat="server"></br>
There is no </br> tag in HTML, so the browser shows it as there are two <br /> tags. Nice way out of this is to create HtmlGenericSelfCloseControl like this (sorry for C# code but you should have no issue with rewritting this in VB.NET):
public class HtmlGenericSelfCloseControl : HtmlGenericControl
{
public HtmlGenericSelfCloseControl()
: base()
{
}
public HtmlGenericSelfCloseControl(string tag)
: base(tag)
{
}
protected override void Render(HtmlTextWriter writer)
{
writer.Write(HtmlTextWriter.TagLeftChar + this.TagName);
Attributes.Render(writer);
writer.Write(HtmlTextWriter.SelfClosingTagEnd);
}
public override ControlCollection Controls
{
get { throw new Exception("Self closing tag can't have child controls"); }
}
public override string InnerHtml
{
get { return String.Empty; }
set { throw new Exception("Self closing tag can't have inner content"); }
}
public override string InnerText
{
get { return String.Empty; }
set { throw new Exception("Self closing tag can't have inner text"); }
}
}
And use it instead:
pDoc.Controls.Add(New Label With {.Text = "whatever"})
pDoc.Controls.Add(New HtmlGenericSelfCloseControl("br"))
As a simpler alternative (if you have reference to the Page) you can try using Page.ParseControl:
pDoc.Controls.Add(New Label With {.Text = "whatever"})
pDoc.Controls.Add(Page.ParseControl("br"))
Related
I implemented a custom tag helper to generate page numbers as a link. The "Process" method is triggered and the string builder has all the HTML content in it but it does not output the result on the screen. It just renders , Could anyone help, please?
<paginate page="Model.PageInfo" />
[HtmlTargetElement("paginate",
TagStructure = TagStructure.WithoutEndTag)]
public class PaginateTagHelper : TagHelper
{
public PageInfo Page { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
StringBuilder html = new StringBuilder();
html.Append("<div>");
for (int i = 1; i <= Page.TotalPages; i++)
{
var tag = new Microsoft.AspNetCore.Mvc.Rendering.TagBuilder("a");
tag.MergeAttribute("href", Page.PageUrl(i));
tag.InnerHtml.AppendHtml(i.ToString());
if (i == Page.CurrentPage)
{
tag.AddCssClass("selected");
tag.AddCssClass("btn-primary");
}
tag.AddCssClass("btn btn-default");
html.Append(GetTagContent(tag));
}
html.Append("</div>");
output.Content.SetHtmlContent(html.ToString());
}
private string GetTagContent(IHtmlContent content)
{
using (var writer = new System.IO.StringWriter())
{
content.WriteTo(writer, System.Text.Encodings.Web.HtmlEncoder.Default);
return writer.ToString();
}
}
}
Remove TagStructure = TagStructure.WithoutEndTag, self closing tags are not suitable for tags with inner content.
Self-closing TagHelpers
Many Tag Helpers can't be used as self-closing tags. Some Tag Helpers are designed to be self-closing tags. Using a Tag Helper that was not designed to be self-closing suppresses the rendered output. Self-closing a Tag Helper results in a self-closing tag in the rendered output.
Instead of rendering the image of a menu item as an <img> tag within an anchor, I'd like to add a class to the anchor tag which adds the image as a background image.
At the moment, I'm doing some post-processing with javascript that searches for image urls. If found, they are removed and replaced with a CSS class.
Is there a way to perform this by overriding the Menu or implementing a MenuAdapter?
I've had a look at the MenuAdapter class but it looks like I'd have to re-implement all rendering functionality just to change this small part.
[Note: the reason I'm doing this is because I want to display the images after the text; i struggled to do this using the default rendering.]
ETA: Answered below.
I found the simplest way is to override the Render method of the Menu.
Using this menu, you can put a tooltip and css class, separated by a semi-colon, in the ToolTip property of the menu item:
item.ToolTip = "this is the tip; class1 class2";
Note: This is a simplistic menu that performs as much as I want it to do. It ignores ImageUrl and SeparatorImageUrl.
public class CSSItemMenu : Menu
{
protected override void Render(HtmlTextWriter writer)
{
this.PerformDataBinding();
writer.Write(string.Format("<div id=\"{0}\" class=\"{1}\">", base.ClientID, base.CssClass));
writer.WriteLine();
writer.WriteLine("<ul class=\"level1\">");
foreach (MenuItem item in Items)
{
WriteItem(writer, item, 1);
}
writer.WriteLine("</ul>");
writer.WriteLine("</div>");
}
private static void WriteItem(HtmlTextWriter writer, MenuItem item, int level)
{
writer.WriteLine("<li>");
string title = "";
var userClass = "";
if (!string.IsNullOrEmpty(item.ToolTip))
{
var data = item.ToolTip.Split(';');
title = string.Format(" title=\"{0}\"", data[0].Trim());
if (data.Length > 1)
{
userClass = " " + data[1].Trim();
}
}
var cssClass = string.Format("class = \"popout level{0}{1}\"", level, userClass);
writer.WriteLine(string.Format("<a {0} href=\"{1}\"{2}>{3}</a>", cssClass, item.NavigateUrl, title, item.Text));
if (item.ChildItems.Count > 0)
{
writer.WriteLine(string.Format("<ul class=\"level{0}\">", level + 1));
foreach (MenuItem child in item.ChildItems)
{
WriteItem(writer, child, level + 1);
}
writer.WriteLine("</ul>");
}
writer.WriteLine("</li>");
}
}
I need to get the latest text set in the custom control by javascript. When i tried to get the selected text from server control, it is always returning the default text & not the modified text. How to retain the latest value set by the javascript in servercontrol? Below is the complete code for your reference..
ServerControl1.cs
[assembly: WebResource("ServerControl1.Scripts.JScript1.js", "text/javascript")]
namespace ServerControl1
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")]
public class ServerControl1 : WebControl
{
public List<string> ListItems
{
get
{
return ViewState["items"] as List<string>;
}
set
{
ViewState["items"] = value;
}
}
public string Text
{
get
{
return (FindControl("middleDiv").FindControl("anchorID") as HtmlAnchor).InnerText;
}
set
{
((FindControl("middleDiv").FindControl("anchorID") as HtmlAnchor)).InnerText = value;
}
}
protected override void CreateChildControls()
{
base.CreateChildControls();
HtmlGenericControl selectedTextContainer = new HtmlGenericControl("div");
selectedTextContainer.ClientIDMode = System.Web.UI.ClientIDMode.Static;
selectedTextContainer.ID = "middleDiv";
HtmlAnchor selectedTextAnchor = new HtmlAnchor();
selectedTextAnchor.ClientIDMode = System.Web.UI.ClientIDMode.Static;
selectedTextAnchor.ID = "anchorID";
selectedTextAnchor.HRef = "";
selectedTextContainer.Controls.Add(selectedTextAnchor);
HtmlGenericControl unList = new HtmlGenericControl("ul");
foreach (string item in ListItems)
{
HtmlGenericControl li = new HtmlGenericControl("li");
HtmlAnchor anchor = new HtmlAnchor();
anchor.HRef = "";
anchor.Attributes.Add("onclick", "updateData()");
anchor.InnerText = item;
li.Controls.Add(anchor);
unList.Controls.Add(li);
}
selectedTextContainer.Controls.Add(unList);
Controls.Add(selectedTextContainer);
ChildControlsCreated = true;
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
string resourceName = "ServerControl1.Scripts.JScript1.js";
ClientScriptManager cs = this.Page.ClientScript;
cs.RegisterClientScriptResource(typeof(ServerControl1), resourceName);
}
}
}
JScript1.js
function updateData() {
var evt = window.event || arguments.callee.caller.arguments[0];
var target = evt.target || evt.srcElement;
var anchor = document.getElementById("anchorID");
anchor.innerText = target.innerText;
return false;
}
TestPage Codebehind
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
List<string> items = GetDataSource();
ServerControl1.ListItems = items;
ServerControl1.Text = "Select ..";
}
}
protected void ClientButton_Click(object sender, EventArgs e)
{
string selectedText = ServerControl1.Text;
}
The server won't get your client changes unless you POST the changes to him. Your HtmlAnchors are being rendered in HTML as <a> controls, and these type of controls won't POST anything to the server.
You're going to need an <input> control to input the changes into the server (that's why they're called input controls after all). I suggest an <input type=hidden> to hold the value of the anchor.innerText and keeps its state.
Your Javascript function needs to be modified so it updates the anchor.innerText AND updates the hidden input value as well. This way when the page gets posted back to the server you can retrieve the updated and client-modified value from the hidden field.
First you need to define as private fields your selectedTextAnchor and the hiddenField you are going to insert. This is because you need to access them in your CreateChildControls method as well as in the getter and setter of yout Text property. Much in the way the partial designer classes define the controls you want to have available in code-behind.
ServerControl.cs
private HtmlAnchor selectedTextAnchor;
private HtmlInputHidden hiddenField;
In the CreateChildControls method you need to insert the hidden field.
You'll notice I removed the use of ClientIDMode.Static. Using that mode would make your client controls to have the same fixed IDs and Javascript might get confused when you have multiple copies of your ServerControl in a page, and thus losing the reusable purpose of a custom control.
Instead, you need to provide your Javascript function with the ClientID's of the controls it needs to modify. The key here is that you need to attach your controls to the Control's hierarchy BEFORE you try to get their ClientID's.
As soon as you do this.Controls.Add(dummyControl), you're making dummyControl to become a part of the Page and its dummyControl.ClientID will be suddenly changed to reflect the hierarchy of the page you're attaching it into.
I changed the order at which your controls are attached to the Control's collection so we can grab their ClientID's at the time we build the onclick attribute and pass the parameters so your Javascript function knows which anchor and hiddenField to affect.
ServerControl.cs
protected override void CreateChildControls()
{
base.CreateChildControls();
// Instantiate the hidden input field to include
hiddenField = new HtmlInputHidden();
hiddenField.ID = "ANCHORSTATE";
// Insert the hiddenfield into the Control's Collection hierarchy
// to ensure that hiddenField.ClientID contains all parent's NamingContainers
Controls.Add(hiddenField);
HtmlGenericControl selectedTextContainer = new HtmlGenericControl("div");
// REMOVED: selectedTextContainer.ClientIDMode = System.Web.UI.ClientIDMode.Static;
selectedTextContainer.ID = "middleDiv";
selectedTextAnchor = new HtmlAnchor();
// REMOVED: selectedTextAnchor.ClientIDMode = System.Web.UI.ClientIDMode.Static;
selectedTextAnchor.ID = "anchorID";
selectedTextAnchor.HRef = "";
selectedTextContainer.Controls.Add(selectedTextAnchor);
// Insert the selectedTextContainer (and its already attached selectedTextAnchor child)
// into the Control's Collection hierarchy
// to ensure that selectedTextAnchor.ClientID contains all parent's NamingContainers
Controls.Add(selectedTextContainer);
HtmlGenericControl unList = new HtmlGenericControl("ul");
foreach (string item in ListItems)
{
HtmlGenericControl li = new HtmlGenericControl("li");
HtmlAnchor anchor = new HtmlAnchor();
anchor.HRef = "";
// The updateData function is provided with parameters that will help
// to know who's triggering and to find the anchor and the hidden field.
// ClientID's are now all set and resolved at this point.
anchor.Attributes.Add("onclick", "updateData(this, '" + selectedTextAnchor.ClientID + "', '" + hiddenField.ClientID + "')");
anchor.InnerText = item;
li.Controls.Add(anchor);
unList.Controls.Add(li);
}
selectedTextContainer.Controls.Add(unList);
}
Note the use of the keyword this in the updateData function, it'll help us to grab the object that is triggering the action. Also note that both Id's are passed as strings (with single quotes)
The Javascript function would need to be modified so it updates the anchor and the hidden input field.
JScript1.js
function updateData(sender, anchorId, hidFieldId) {
// Update the anchor
var anchor = document.getElementById(anchorId);
anchor.innerText = sender.innerText;
// Update the hidden Input Field
var hidField = document.getElementById(hidFieldId);
hidField.value = sender.innerText;
return false;
}
The last thing to do is change the way you are setting and getting your Text property.
When you GET the property you need to check if it's a Postback, and if it is, then you want to check if among all the info that comes from the browser there is your HiddenInputField. You can grab all the info coming from the client right at the Request object, more specifically, in the Request.Form.
All enabled input controls on your page will be part of the Request.Form collection, and you can get their values by using Request.Form[anyInputControl.UniqueID]. Note that the key used for this object is the UniqueID, NOT ClientID.
Once you get your client-modified value from the hidden input, you assign its value to the selectedTextAnchor, otherwise it'll go back to the original "Select..." text.
When you SET the property, you just need to assign it to the selectedTextAnchor.
In both GET and SET you need to call EnsureChildControls(), which will actually call your CreateChildControls() to make sure that your selectedTextAnchor and hiddenField controls are instantiated before you try to get some of their properties. Pretty much the same way that it's done in Composite Controls.
ServerControl.cs
public string Text
{
get
{
EnsureChildControls();
if (this.Page.IsPostBack)
{
string HiddenFieldPostedValue = Context.Request.Form[hiddenField.UniqueID];
// Assign the value recovered from hidden field to the Anchor
selectedTextAnchor.InnerText = HiddenFieldPostedValue;
return HiddenFieldPostedValue;
}
else
{
return selectedTextAnchor.InnerText;
}
}
set
{
EnsureChildControls();
selectedTextAnchor.InnerText = value;
}
}
This way you can have a control that recognizes the changes made in client. Remember that server won't know any change in client unless you notice him.
Another approach would be to notice the server everytime you click a link through an ajax request, but this would require a whole new different code.
Good luck!
I guess what I want to do is "chain" my data down so that it ends up looking the same.
All my html must be wrapped in some form of
<fieldset class="" data-role="">
So what I have is a helper that prints the various forms. One would be a label:
<fieldset data-role="#role">
<label>#Html.Raw(label)</label>
</fieldset>
Now when I have multiple types of labels, and one includes being a code block. When it is a
simple piece of text, like "First Name" I do:
#FieldSet.Label("First Name")
But when I have a code block such as:
<b>some text</b>
<p>some other text (some time frame - some time frame)
It becomes complicated to use this:
#FieldSet.Label("<b>" + Model.Text1 + "</b><p>" + Model.Text2 +
" (" + Model.Time1 + " - " + Model.Time2 +")</p>")
What I want it a solution that looks something like this:
#FieldSet.Label(#<text>
<b>#Model1.Text1</b>
<p>#Model.Text2 (#Model.Time1 - #Model.Time2)</p>
</text>)
I read somewhere this was possible, but I cannot find the article. I could be completely misled, but I really don't want to have a single piece of HTML in the code behind and I want to utilize the razor syntax, not string concatenation.
Check this articles from Phil Haack
http://haacked.com/archive/2011/02/27/templated-razor-delegates.aspx
http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx
You could:
Write as an extension method to a strongly-typed HtmlHelper:
public static class RazorExtensions
{
public static HelperResult Label<T>(this HtmlHelper<T> helper, Func<T, HelperResult> template) {
return new HelperResult(writer => {
writer.Write("<label>");
template(helper.ViewData.Model).WriteTo(writer);
writer.Write("</label>");
});
}
}
So you could write
#Html.Label(#<text><span>#Model.Item1<span><strong>#Model.Item2</strong></text>)
Pass Model as a parameter to your helper method
public static class FieldSet
{
public static HelperResult Label<T>(this T model, Func<T, HelperResult> template) {
return new HelperResult(writer => {
writer.Write("<label>");
template(model).WriteTo(writer);
writer.Write("</label>");
});
}
}
Usage:
#FieldSet.Label(Model, #<div><span>#Model.UserName</span><strong>#Model.FullName</strong><p>#Model.Description</p></div>)
You could look at how the #Html.BeginForm is implemented.
Create a class that implements IDisposable, and that writes to the Response stream directly:
Your code could look like this (entered by head, not tested):
class FieldSet : IDisposable {
public FieldSet(string label) {
// TODO: Encode label on line below
HttpContext.Current.Response.Write(string.Format("<fieldset><label =\"{0}\"", label));
}
public void Dispose() {
HttpContext.Current.Response.Write("</fieldset>");
}
}
static class FieldSetExtionsions {
public static FieldSet FieldSet(this HtmlHelper html, string label) {
return new FieldSet(label);
}
}
The usage will be:
#using (Html.FieldSet("your label")) {
<div>
Your razor code goes here
</div>
}
Using ASP.Net, I have a server control for which i would like to add the inline css style "background-image:none". However, when i call:
writer.AddStyleAttribute("background-image", "none");
The following inline style is generated (and tries to resolve the url "none"):
background-image:url(none)
Is there a special syntax I can use to set the background image to none inline?
Looking at the code for the HTMLTextWriter and CssTextWriter classes in .NET Reflector, the only thing I can think of is subclassing HTMLTextWriter yourself.
"Binary not the first element in the style enum", ~HtmlTextWriterStyle.BackgroundColor, is what it uses for any style whose name it doesn't recognize, and therefore doesn't bother to check if the value needs wrapped in "url()" when it's actually written out.
HtmlTextWriterEx isn't the greatest name, but whatever. Depending on what you're doing, you might(?) need to do something like this in your code-behind System.Web.UI.Page subclass:
protected override HtmlTextWriter CreateHtmlTextWriter(TextWriter writer)
{
return new HtmlTextWriterEx(writer);
}
And here's the class:
class HtmlTextWriterEx : HtmlTextWriter
{
public HtmlTextWriterEx(TextWriter writer)
: this(writer, "\t")
{
}
public HtmlTextWriterEx(TextWriter writer, string tabString)
: base(writer, tabString)
{
}
public override void AddStyleAttribute(string name, string value)
{
if (name.ToLower() == "background-image" && value.ToLower() == "none")
base.AddStyleAttribute(name, value, ~HtmlTextWriterStyle.BackgroundColor);
else
base.AddStyleAttribute(name, value);
}
public override void AddStyleAttribute(HtmlTextWriterStyle key, string value)
{
if(key == HtmlTextWriterStyle.BackgroundImage && value.ToLower() == "none")
base.AddStyleAttribute("background-image", value, ~HtmlTextWriterStyle.BackgroundColor);
else
base.AddStyleAttribute(key, value);
}
}
You could try adding a CSS class to your page such as
.noimage { background-image: none; }
Then instead of adding the style attribute in your code behind you could add the CssClass.