I need to develop a template-like user control, which would accept any arbitrary content, including other controls.
WmlCard.ascx
<asp:PlaceHolder ID="phContent" runat="server">
<card id="<%= Me.CardId %>" title="<%= Me.Title %>">
<p>
<%= Me.InnerText %>
</p>
</card>
</asp:PlaceHolder>
It would be used this way:
ListaTelefonica.aspx
<%# Register TagPrefix="sic" TagName="Card" Src="~/path/to/WmlCard.ascx"%>
<sic:Card ID="BuscaCard" runat="server" CardId="busca" title="Lista TelefĂ´nica" Visible="false">
<asp:Literal ID="SomeLiteral" runat="server" Visible="false">Some text<br /></asp:Literal>
<asp:Literal ID="RuntimeFilledLiteral" runat="server" Visible="false" /><br /></asp:Literal>
Any text.
</sic:Card>
It is unfortunate that Ascx user controls have to inherit System.Web.UI.UserControl. If I could inherit System.Web.UI.WebControls.WebControl, for example, it would be a piece of cake.
My problem is similar to this one, but instead of just text the control should accept any other control inside it.
I tried <ParseChildren(False)> and overriding AddParsedSubObject(Object) in WmlCard, but it is not a solution because the html is rendered before the Page Load, making it pointless to change the value of RuntimeFilledLiteral.Text in a page's Page_Load, for example.
First error I got:
Parser Error Message: Type 'ASP.sic_sicphone_usercontrol_wmlcard_ascx' does not have a public property named 'Literal'.
After adding <PersistenceMode(PersistenceMode.InnerDefaultProperty), ParseChildren(True, "InnerText")>:
The 'InnerText' property of 'sic:Card' does not allow child objects.
Same as above but changing WmlCard's property InnerText type from String to List(Of Object):
Literal content ('Any text.') is not allowed within a 'System.Collections.IList'.
After adding <ParseChildren(False)> and overriding AddParsedSubObject:
No error message, but WmlCard's content is rendered to html before I have a chance to change its inner controls runtime properties.
If I change WmlCard to inherit System.Web.UI.WebControls.PlaceHolder instead of System.Web.UI.UserControl:
'...UserControl.WmlCard' is not allowed here because it does not extend class 'System.Web.UI.UserControl'.
Found a valid way:
1) Put nothing inside my control (WmlCard.ascx).
2) Added the attribute <ParseChildren(False)> to WmlCard.
3) Added a handler to the event PreRender on WmlCard:
Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
Dim litBefore As New Literal()
litBefore.Text = String.Format("<card id=""{0}"" title=""{1}""><p>", Me.CardId, Me.Title)
Dim litAfter As New Literal()
litAfter.Text = "</p></card>"
Me.Controls.AddAt(0, litBefore)
Me.Controls.Add(litAfter)
End Sub
And that's it. Everything is working. Thanks for your help thinking thru this!
What you need is a templated user control.
Take a look at this article for a detailed how-to: http://msdn.microsoft.com/en-us/library/36574bf6.aspx.
Actually you could create a custom user control, inherit from placeholder and your problem should be solved?
You can go the templated control way as suggested but I don't see the reason why you would want to do that.
[SupportsEventValidation, DefaultEvent("YourEventName")]
[ParseChildren(true)]
[PersistChildren(false)]
[ToolboxBitmap(typeof(System.Web.UI.WebControls.Panel))]
public class MyCustomControl : System.Web.UI.WebControls.PlaceHolder, INamingContainer, IPostBackDataHandler, IPostBackEventHandler
you can override the Render method to control the rendered HTML. I am struggling to see youru actual problem.
Got this working simply enough.
[ParseChildren(false)]
public class CssControl : System.Web.UI.UserControl
{
protected override void Render(HtmlTextWriter writer)
{
string html = null;
using(var innerWriter = new System.IO.StringWriter())
using(var htmlWriter = new HtmlTextWriter(innerWriter))
{
base.Render(htmlWriter);
html = innerWriter.GetStringBuilder().ToString();
}
var min = ScriptAndCssParser.MinifyCssFromHtml(html);
writer.Write(min);
}
}
In my case my goal was allow a master page level control to have child HTML and server-controls, like asp:content blocks. That way Viewpages could pass stylesheets up the chain to the master page.
Then I wanted to intercept all the generated HTML, and minify the CSS files. The minification part is out of scope for this question, but the above ParseChildren(false) prevents the "element can't contain..." error in the .aspx, and then calling base.Render() on an inner writer allows you to use the existing Web Forms pipeline to do all the rendering for you of both server-controls and plain HTML content.
Related
I'm seeking advice on how to best encapsulate this HTML markup and behavior for extensive reuse.
I've developed a working version of some "context sensitive" help on a given .aspx page that uses the qTip2 plugin for jQuery ("squeezed up" here for completeness for the question):
$('a.myMore[title]').qtip({
content: { title: { button: true } }
, style: { classes: 'qtip-youtube myCustomClass' }
, position: { target: 'mouse', adjust: { x: 5, y: 5} }
, show: { effect: function() { $(this).fadeTo(500, 1); } }
, events:{hide:function(event, api) {if (event.originalEvent.type !== 'click') return false;}}
});
Here is a sample of the the markup operated upon by the plugin above that I'd like to encapsulate:
<a class="myMore" href="#" title='Text here will be displayed by a jQuery plugin called qTip2'>
<img src='../images/icon/info1.png' alt='?' />
</a>
This is in an older environment of Visual Studio 2008 webforms using VB.Net. One thread on SO expressed my need very well - see MVC3 Razor views - but the only encapsulation approach I can think of is writing an ASP.Net web user control (.ascx) to dump out the HTML I need. I envision being able to sprinkle something like this throughout my .aspx pages:
<qTipH:qTipHelper Title="Here is some content for the qTip." runat="server" />
Am I on the right track given the "old-ish" environment in which I have to work?
I started in on writing this control and immediately noticed there is no title attribute for any of these server controls - <asp:HyperLink nor <asp:ImageButton nor <asp:LinkButton. I don't think the code-behind of the user control can inject the title attribute text into a standard HTML <a> tag.
I think I know what I need rendered but I'm looking for advice on the best approach. Thanks.
EDIT - UPDATE:
So here is my .ascx file I came up with:
<%# Control Language="vb" AutoEventWireup="false"
CodeBehind="qTipHelper.ascx.vb"
Inherits="myProjectName.qTipHelper" %>
<asp:HyperLink ID="HyperLink1" runat="server" CssClass="myMore"></asp:HyperLink>
I discovered that any "missing" attributes like "title" can be handled by the following code-behind which adds to the Attributes collection of the control:
Public Partial Class qTipHelper
Inherits System.Web.UI.UserControl
Public Title As String = Nothing
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Title Is Nothing OrElse Title = "" Then
Visible = False
Else
Dim encodedTitle As String = Server.HtmlEncode(Title)
HyperLink1.Attributes.Add("title", encodedTitle)
HyperLink1.Attributes.Add("href", "#")
HyperLink1.ImageUrl = "../images/icon/info1.png"
End If
End Sub
End Class
On my invoking page(s), I added this:
<%# Register TagPrefix="qTip" TagName="qTipHelper" Src="~/UserControls/qTipHelper.ascx" %>
and then to spit out the HTML, I added this:
<qTip:qTipHelper runat="server" title="grist for the mill courtesy of qTip" >
</qTip:qTipHelper>
Is there a coding technique that could be added to the user control that would emit one Javascript script block at the bottom of the page (assuming that one or more tags for qTipHelper were present in the HTML above)?
In retrospect, this is weird question (I think because I never had to do anything like this before)...
Anyway, I simply collected all my current uses of qtip in a single Javascript file which I reference at the bottom of each .aspx page that uses it like this:
<script type="text/javascript" src="<%= ResolveClientUrl("~/js/qTipCode.js") %>"></script>
So, this boils down to:
1. writing a single user control
2. registering the user control on .aspx content pages that will need it
3. sprinkling the .aspx content page with the user control tag whereever it is needed
4. adding the script tag to point to the qtip Javascript file
5. ensuring that the selectors in the jQuery "match up" with the HTML markup on the page
I hope this helps someone else. I have learned alot about new stuff trying this.
I load a piece of html which contains something like:
<em> < input type="text" value="Untitled" name="ViewTitle" id="ViewTitle" runat="server"> </em>
into my control. The html is user defined, do please do not ask me to add them statically on the aspx page.
On my page, I have a placeholder and I can use
LiteralControl target = new LiteralControl ();
// html string contains user-defined controls
target.text = htmlstring
to render it property. My problem is, since its a html piece, even if i know the input box's id, i cannot access it using FindControl("ViewTitle") (it will just return null) because its rendered as a text into a Literal control and all the input controls were not added to the container's control collections. I definitely can use Request.Form["ViewTitle"] to access its value, but how can I set its value?
Jupaol's method is the prefer way of adding dynamic control to a page.
If you want to insert string, you can use ParseControl.
However, it doesn't cause compilation for some controls such as PlaceHolder.
Your process is wrong, you are rendering a control to the client with the attribute: runat="server"
This attribute only works if the control was processed by the server, you are just rendering as is
Since your goal is to add a TextBox (correct me if I'm wrong), then why don't you just add a new TextBox to the form's controls collection???
Something like this:
protected void Page_Init(object sender, EventArgs e)
{
var textbox = new TextBox { ID="myTextBoxID", Text="Some initial value" };
this.myPlaceHolder.Controls.Add(textbox);
}
And to retrieve it:
var myDynamicTextBox = this.FindControl("myTextBoxID") as TextBox;
I have created several working examples and they are online on my GitHub site, feel free to browse the code
I have a user control defined like this
<%# Control .....
<Test:MyCustomControl id="xxx" runat="server>
</Test:MyCustomControl>
I would like to use this control on a page like
<Tag:MyControl runat="server">
<div>
my html
</div>
</Tag...
In my custom control codebehind I would like to read the inner html and set it to a property of Test:MyCustomcontrol
Currently I am getting an error saying that "...does not have property div"
How can I do this?
Note: For clarification the inner html can be an arbitrary html, so I need a way to read anything that user has typed in the page.
you can expose the div(running on the server) as a property from your UserControl
on the usercontrol html:
<div id="dvSomething" runat="server"></div>
on ur usercontrol codebehind ".cs file":
public System.Web.UI.HtmlControls.HtmlGenericControl TheDiv
{
get
{
return dvSomething;
}
set {
dvSomething = value;
}
}
on the page that contains the usercontrol:
WebUserControl11.TheDiv.InnerHtml = "addin something to div from page";
good luck
I will keep the other question open in case someone need a different solution:
I'm not sure about your requeriments like but here are two solutions, I hope this is what you want:
one adding control(you can add any by the way, not only textbox) and other pure html as per you described
TextBox txtAdd = new TextBox();
txtAdd.Style.Add("width", "200px");
WebUserControl11.Controls.Add(txtAdd);
TextBox txtRead = (TextBox)WebUserControl11.Controls[1];
((System.Web.UI.HtmlControls.HtmlGenericControl)WebUserControl11.Controls[0]).InnerHtml = "<b>something</b>";
string currentHtml = ((System.Web.UI.HtmlControls.HtmlGenericControl)WebUserControl11.Controls[0]).InnerHtml;
of course, the index will change depending how many elements you have on your user control
I am using a contentplaceholder control in a master page to allow the content editor to specify the URL of an image used as a background to a div.
<div id="content-left-column"
style="background-image: url('<wc:UrlContentPlaceHolder runat='server' ID='leftContentBackgroundUrl'></wc:UrlContentPlaceHolder>');">
The placeholder is referenced on the content page like:
<asp:Content ID="Content1" ContentPlaceHolderID="leftContentBackgroundUrl" runat="server">/img/left-content.jpg</asp:Content>
The page renders just fine using this approach. However, when I look at the content page in source view, the ContentPlaceHolderId attribute value is underlined and there is a warning "Could not find 'leftContentBackgroundUrl' in the current master page or pages."
Design view will not render the content page due to this error.
Is there a way to use ContentPlaceHolder for attribute values such that no errors are reported in the Visual Studio editor and design surface?
*Note. I am aware of the issues with this approach. If the content editor puts in spaces, carriage returns or performs a document format in visual studio, the rendered attribute value is broken. I have created a subclass of ContentPlaceHolder that trims its values and uses ResolveClientUrl to address these issues. For the sake of discussion I have described the issue which affects a normal ContentPlaceHolder control.
The following stack overflow question addresses the fact that ContentPlaceHolder can be used with attribute values but does not address the design surface issues.
Why can't I use a ContentPlaceholder inside HTML attributes in ASP.NET?
I don't believe that's how ContentPlaceHolders where meant to be used. I would strongly advise you to use inline code for this.
Main.master:
<div id="content-left-column"
style="background-image: url(<%: LeftContentBackgroundURL %>);">
Main.master.cs:
public string LeftContentBackgroundURL { get; set; }
In the ContentPage you then just use the #MasterType directive and set the Property in Codebehind.
Content.aspx:
<%# MasterType VirtualPath="~/Main.master" %>
Content.aspx.cs:
protected void Page_Load(object sender, EventArgs e)
{
this.Master.LeftContentBackgroundURL = "/img/left-content.jpg";
}
This solution was inspired by #atticae's response.
On the master page, I included a normal ContentPlaceholder control with visible="false". Where I was previously using that ContentPlaceholder as an attribute value, I instead reference a property of the MasterPage, LeftBackgroundImageUrl.
<asp:ContentPlaceHolder runat='server' ID='leftContentBackgroundUrl' Visible="false"/>
<div id="content-left-column" style="background-image: url('<%: LeftBackgroundImageUrl%>');">
Using a subclass of ContentPlaceholder causes errors in the design surface of content pages which is why I went back to using a normal ContentPlaceholder.
The LeftBackgroundImageUrl property code looks like:
ReadOnly Property LeftBackgroundImageUrl As String
Get
Return RenderResolvedUrl(leftContentBackgroundUrl)
End Get
End Property
Private Function RenderedResolvedUrl(control As Control) As String
Dim visible As Boolean = control.Visible
control.Visible = True
Dim result As String = Nothing
Using writer As New System.IO.StringWriter()
Using htmlWriter As New System.Web.UI.HtmlTextWriter(writer)
control.RenderControl(htmlWriter)
htmlWriter.Flush()
End Using
result = Page.ResolveClientUrl(writer.ToString.Trim).Trim
End Using
control.Visible = visible
Return result
End Function
This solution allows the image url to be specified declaratively, and without the user having to add a MasterType directive. This is not perfect in the sense that it does not, at edit/design time, validate that the content the editor provides is just a URL or application relative URL. But, it does keep the user from having to write code.
I desingned a page with tags, Now I want to access object tags in code behind.
This is aspx page code....
<object type="label" runat="server" class="System.Web.UI.WebControls.Label" id="label_priority" parent="-1" bindedfield="priority" empty="1" value="MyValue">
Here I am adding runat=server in object tags it is giving error as
"An object tag must contain a Class, ClassID or ProgID attribute."
then I added class="System.Web.UI.WebControls.Label", now not giving any error but not showing anything in browser.
so My question is how do I access object tags in aspx.cs page?
or
I want to create a label with object tag that is accessible in code behind.
Sujeet
When you have runat="server" on a <object/> or <script/> tag, .NET expects it to be a server side object. You can however create the entire markup on the server side. Assuming you have <div id="somecontainer" runat="server"></div> in your page:
protected void Page_Load(object sender, EventArgs e)
{
HtmlGenericControl myObject = new HtmlGenericControl();
myObject.TagName = "object";
myObject.Attributes.Add("data", "somevideo.mpeg");
Page.FindControl("somecontainer").Controls.Add(myObject);
}
The result is:
<div id="somecontainer"><object data="somevideo.mpeg"></object></div>
You can use the same method to add elements inside the object, for instance <param/> tags.
You can use below like code in your page load event if you want to access this in page load :
ScriptManager.RegisterStartupScript(Page,Page.GetType(), "Script", "document.getElementById('DIVID').style.display='none'",true);
Here we are changing the display property of the div .you can change according to your requirement.
Hope this will help you.
you can do something like this
<object id="items" class = "System.Collections.ArrayList" runat="server">
as it is run at server now , you can accessing from code behind