I have two pages, let's call them "receipts.com" and "business.receipts.com". Both link to a page on a different domain via Response.Redirect("http://receipts2.com/default.aspx?mode=")
where the "mode"-parameter is the referring page.
The recieving page should look in the query string, and choose a different CSS class according to the "mode"-parameter.
How is this accomplished? And is this the right way to do it?
Instead of swapping class names you can use the same class and different stylesheets.
There are two ways to handle stylesheets: client side and server side.
On the client side, you can parse the query string and disable stylesheets using: (document.getElementsByTagName("link")[i]).disabled = true;
On the server side, you can use themes or simply add a placeholder around the style declarations and show/hide them using codebehind that looks at Response.QueryString["mode"]:
<asp:PlaceHolder ID="placeHolder1" runat="server" Visible="false">
<link rel="stylesheet" href="/alternate.css" media="all" />
</asp:PlaceHolder>
and code behind in a master page somewhere:
if(Response.QueryString["mode"] == "blah")
{
placeHolder1.Visible = true;
}
You could use different Page-Themes:
protected void Page_PreInit(object sender, EventArgs e)
{
switch (Request.QueryString["mode"])
{
case "receipts.com":
Page.Theme = "DefaultTheme";
break;
case "business.receipts.com":
Page.Theme = "BusinessTheme";
break;
}
}
Of course you can also use the code above to apply different Css-Classes to controls.
You can use document.referrer instead of passing in the page via querystring.
When you say "The receiving page should look in the query string, and choose a different CSS class", what is that class going to be set against, ie the body or an element like p?
Plain Javascript
document.getElementById("MyElement").className = " yourClass";
jQuery
$("p").addClass("yourClass");
Perhaps you meant a css theme?
and then you could try
if (document.referrer == "blahblah")
document.write("<link rel='stylesheet' type='text/css' href='one.css' />)
else
document.write("<link rel='stylesheet' type='text/css' href='two.css' />)
Although I would recommend looking into jQuery
$.get(stylesheet, function(contents){
$("<style type=\"text/css\">" + contents + "</style>").appendTo(document.head);
});
u can do something like this
{
string hrefstring = null;
string mode = this.Page.Request.QueryString.Item("mode");
if (mode == "a") {
hrefstring = ("~/yourcss/a.css");
} else if (mode == "b") {
hrefstring = ("~/yourcss/b.css");
}
css.Href = ResolveClientUrl(hrefstring);
css.Attributes("rel") = "stylesheet";
css.Attributes("type") = "text/css";
css.Attributes("media") = "all";
Page.Header.Controls.Add(css);
}
Related
I want to develop a way in which it would be possible to wrap a .Net MVC web application with the correct look and feel of the site to which it is to be linked to.
Basically I want to store a URL of a 'reference page' for the encompassing site which my application will use to screenscrape the header/footer HTML from to use in its Master Page.
So, if/when the site (output from a CMS) changes its structure/images/colours my application will simply use the newly created 'template' and wrap itself accordingly.
There are set start/end div tags in the 'template' being used so I just need to screenscrape the HTML, split it at the relevant points and somehow inject it into the MasterPage for my app.
The screenscraping part looks reasonably straightforward, it's the injection into the Master Page which I am having problems sorting out.
Any help would be much appreciated. :)
EDIT - I am currently planning this in my head and have no code to post. As I say, the screenscraping part looks fine, but how would I go about inserting/injecting the relevant HTML extracted from the 'reference page' for the header/footer into the Master Page being used by my application?
I know you probably already solved this but here is a solution that works with master pages and MVC (and ASP.Net forms as well).
I first tried overriding the Render method of the master page and using RenderControl to render the ContentPlaceHolders, and replacing certain tags in the template with the rendered result. This works for ASP.Net forms but doesn't work for MVC - this way <% using (Html.BeginForm("A","B")) { %> always results in a form tag being rendered at the very top of the page, before the doctype.
Solution
Retrieve the template and split it into its parts, some are text parts and some are placeholder parts. In your master page, you have a HTML document and your placeholders - not only your placeholders. This way the VS designer will not complain. When rendering, however, you start by clearing the Controls collection and then add each part as either a LiteralControl or a ContentPlaceHolder. You just leave the actual rendering to ASP.Net. Below is code for inspiration.
Master page:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title runat="server"></title>
<asp:PlaceHolder ID="HeadPlaceHolder" runat="server">
<script type="text/javascript" src="/cnnet/Resources/Js/jquery-1.8.1.min.js"></script>
</asp:PlaceHolder>
<asp:ContentPlaceHolder ID="HeadContentPlaceHolder" runat="server"/>
</head>
<body>
<asp:ContentPlaceHolder ID="MainContentPlaceHolder" runat="server" />
</body>
</html>
Master page code-behind:
private HtmlHead originalPageHeader;
static readonly Regex HeadStartRegex = new Regex(#"^\s*<head[^>]*>");
static readonly Regex HeadEndRegex = new Regex(#"</head>\s*$");
static readonly Regex TitleRegex = new Regex(#"<title>[^<]*</title>");
public Default() { Init += Default_Init; }
private void Default_Init(object sender, EventArgs e) { DoScraping(); }
protected override void Render(HtmlTextWriter writer)
{
// get content from html head control generated via Page.Header:
string headHtml = RenderControl(originalPageHeader);
Controls.Remove(originalPageHeader);
headHtml = HeadStartRegex.Replace(headHtml, string.Empty);
headHtml = HeadEndRegex.Replace(headHtml, string.Empty);
headHtml = TitleRegex.Replace(headHtml, string.Empty);
// head.Controls.Add(new LiteralControl(headHtml)); doesnt work if head content placeholder contains code blocks (i.e. <% ... %>)
// Instead add content this way:
int headIndex = Controls.IndexOf(HeadContentPlaceHolder);
if (headIndex != -1)
Controls.AddAt(headIndex + 1, new LiteralControl(headHtml));
base.Render(writer);
}
private void DoScraping()
{
IList<PagePart> parts = ... // do your scraping and splitting into parts
Controls.Clear();
foreach (PagePart part in parts)
{
var literalPart = part as LiteralPart;
if (literalPart != null)
{
Controls.Add(new LiteralControl(literalPart.Text));
}
else
{
var placeHolderPart = part as PlaceHolderPart;
switch (placeHolderPart.Type)
{
case PlaceHolderType.Title:
Controls.Add(new LiteralControl(HttpUtility.HtmlEncode(Page.Title)));
break;
case PlaceHolderType.Head:
Controls.Add(HeadPlaceHolder);
Controls.Add(HeadContentPlaceHolder);
break;
case PlaceHolderType.Main:
Controls.Add(new LiteralControl("<div class='boxContent'>"));
Controls.Add(MainContentPlaceHolder);
Controls.Add(new LiteralControl("<div/>"));
break;
}
}
}
}
private string RenderControl(Control control)
{
string innerHtml;
using (var stringWriter = new StringWriter())
{
using (var writer = new HtmlTextWriter(stringWriter))
{
control.RenderControl(writer);
writer.Flush();
innerHtml = stringWriter.ToString();
}
}
return innerHtml;
}
Parts:
public class PagePart {}
public class LiteralPart : PagePart
{
public LiteralPart(string text) { Text = text; }
public string Text { get; private set; }
}
public class PlaceHolderPart : PagePart
{
public PlaceHolderPart(PlaceHolderType type) { Type = type; }
public PlaceHolderType Type { get; private set; }
}
public enum PlaceHolderType { Title, Head, Main }
For splitting:
class PlaceHolderInfo
{
public PlaceHolderInfo(PlaceHolderType type, Regex splitter)
{
Type = type;
Splitter = splitter;
}
public PlaceHolderType Type { get; private set; }
public Regex Splitter { get; private set; }
}
private static readonly List<PlaceHolderInfo> PlaceHolderInfos = new List<PlaceHolderInfo>
{
new PlaceHolderInfo(PlaceHolderType.Title, new Regex(TitleString)),
new PlaceHolderInfo(PlaceHolderType.Head, new Regex(HeadString)),
new PlaceHolderInfo(PlaceHolderType.Main, new Regex(MainString)),
};
private static List<PagePart> SplitPage(string html)
{
var parts = new List<PagePart>(new PagePart[] { new LiteralPart(html) });
foreach (PlaceHolderInfo info in placeHolderInfos)
{
var newParts = new List<PagePart>();
foreach (PagePart part in parts)
{
if (part is PlaceHolderPart)
{
newParts.Add(part);
}
else
{
var literalPart = (LiteralPart)part;
// Note about Regex.Split: if match is found in beginning or end of string, an empty string is returned in corresponding end of returned array.
string[] split = info.Splitter.Split(literalPart.Text);
for (int i = 0; i < split.Length; i++)
{
newParts.Add(new LiteralPart(split[i]));
if (i + 1 < split.Length) // If result of Split returned more than one string, it means there was a match and we insert the placeholder between each string
newParts.Add(new PlaceHolderPart(info.Type));
}
}
}
parts = newParts;
}
return parts;
}
Note that this solution is easy to expand to more placeholders (breadcrumb, menu, you name it). It makes no assumption as to the order of the placeholders in the template or to the existance of them.
Edit 1:
I originally called DoScraping from the Render method. It turned out to be problematic because it did renumbering of control names in web forms (such as ctl00$MainContentPlaceHolder$RequestingRepeater$ctl01$ctl01). It messed up the numbers to the point where OnCommand in buttons inside repeaters stopped working. Reordering of controls has to happen as early as possible to avoid this so it has been moved to Init now.
Edit 2:
Some pages use Page.Header to generate style and script tags. To support this functionality I have added some hacks to keep the original <head> tag and insert the generated content when rendering.
After a user clicks the log out button I have it take them to a redirection page which displays a message and says " Logging out and Redirecting in ? seconds." I am using
Response.AddHeader("REFRESH", "3;URL=Login.aspx");
is there a way to display how many seconds are left until they are redirected in label?
In your redirect page you need to use JavaScript to handle it.
This sample may help you: http://javascriptsource.com/navigation/countdown-redirect.html
Following up on my comment, you could accomplish your solution via the following code:
<script type="text/javascript">
var timeLeft = 3;
function decrementCounter() {
if (timeLeft > 0) {
document.all('countDown').innerHTML = "Redirecting in " + timeLeft + "...";
timeLeft--;
setTimeout("decrementCounter()", 1000);
}
else {
window.location = "http://www.google.com/"
}
}
</script>
<body>
<form>
<label id="countDown">3</label>
<input type="button" value="Display alert box in 3 seconds" onclick="decrementCounter()" />
</form>
</body>
For example ,
When you Click the Logout button , You can create a count down javascript by dynamic
protected void OnLogout(object sender, EventArgs e)
{
string url = "~/Login.aspx";
string msg = "Logging out and Redirecting in ? ";
StringBuilder js = new StringBuilder("<script language=\"javascript\">")
.Append("var ts = 3; setInterval(\"redirect()\",1000);")
.Append("function redirect(){ if(ts == 0){")
.Append("window.location.href=\"" + url + "\"; }else{")
.Append("document.body.innerHTML = \"msg \" + (ts--)+\"seconds\";}}")
.Append("</script>");
Response.Write(js.ToString());
}
You'll need to use a client-side technology, I.e. JavaScript. Using server-side will require calls to the server which is not needed for something simple like this.
Because you are using Microsoft aspx, you can create a label on the client side and implement an AJAX by giving your code-behind the Id of the label and changing its value base on the time left.
I have a search textbox situated on a masterpage like so:
<asp:TextBox ID="frmSearch" runat="server" CssClass="searchbox"></asp:TextBox>
<asp:LinkButton ID="searchGo" PostBackUrl="search.aspx" runat="server">GO</asp:LinkButton>
The code behind for the search page has the following to pick up the textbox value (snippet):
if (PreviousPage != null && PreviousPage.IsCrossPagePostBack)
{
Page previousPage = PreviousPage;
TextBox tbSearch = (TextBox)PreviousPage.Master.FindControl("frmSearch");
searchValue.Text = tbSearch.Text;
//more code here...
}
All works great. BUT not if you enter a value whilst actually on search.aspx, which obviously isn't a previous page. How can I get round this dead end I've put myself in?
If you use the #MasterType in the page directive, then you will have a strongly-typed master page, meaning you can access exposed properties, controls, et cetera, without the need the do lookups:
<%# MasterType VirtualPath="MasterSourceType.master" %>
searchValue.Text = PreviousPage.Master.frmSearch.Text;
EDIT: In order to help stretch your imagination a little, consider an extremely simple property exposed by the master page:
public string SearchQuery
{
get { return frmSearch.Text; }
set { frmSearch.Text = value; }
}
Then, through no stroke of ingenuity whatsoever, it can be seen that we can access it like so:
searchValue.Text = PreviousPage.Master.SearchQuery;
Or,
PreviousPage.Master.SearchQuery = "a query";
Here is a solution (but I guess its old now):
{
if (PreviousPage == null)
{
TextBox tbSearch = (TextBox)Master.FindControl("txtSearch");
searchValue.Value = tbSearch.Text;
}
else
{
TextBox tbSearch = (TextBox)PreviousPage.Master.FindControl("txtSearch");
searchValue.Value = tbSearch.Text;
}
}
I need my MasterPage to be able to get ControlIDs of Controls on ContentPages, but I cannot
use <%= xxx.CLIENTID%> as it would return an error as the control(s) might not be loaded by the contentplaceholder.
Some controls have a so called BehaviourID, which is exactly what I would need as they can be directly accessed with the ID:
[Asp.net does always create unique IDs, thus modifies the ID I entered]
Unfortunately I need to access
e.g. ASP.NET Control with BehaviouraID="test"
....
document.getElementById("test")
if I were to use e.g. Label control with ID="asd"
....
document.getElementById('<%= asd.ClientID%>')
But if the Labelcontrol isn't present on the contentpage, I of course get an error on my masterpage.
I need a solution based on javascript. (server-side)
Thx :-)
You could use jQuery and access the controls via another attribute other than the ID of the control. e.g.
<asp:Label id="Label1" runat="server" bid="test" />
$('span[bid=test]')
The jQuery selector, will select the span tag with bid="test". (Label renders as span).
Best solution so far:
var HiddenButtonID = '<%= MainContent.FindControl("btnLoadGridview")!=null?
MainContent.FindControl("btnLoadGridview").ClientID:"" %>';
if (HiddenButtonID != "") {
var HiddenButton = document.getElementById(HiddenButtonID);
HiddenButton.click();
}
Where MainContent is the contentplace holder.
By http://forums.asp.net/members/sansan.aspx
You could write an json-object with all the control-ids which are present on the content-page and "register" that object in the global-scope of your page.
Some pseudo pseudo-code, because I can't test it at the moment...
void Page_Load(object sender,EventArgs e) {
System.Text.StringBuilder clientIDs = new System.Text.StringBuilder();
IEnumerator myEnumerator = Controls.GetEnumerator();
while(myEnumerator.MoveNext()) {
Control myControl = (Control) myEnumerator.Current;
clientIDs.AppendFormat("\t\"{0}\" : \"{1}\",\n", myControl.ID, myControl.ClientID);
}
page.ClientScript.RegisterStartupScript(page.GetType(),
"ClientId",
"window.ClientIDs = {" + clientIDs.ToString().Substring(0, clientIDs.ToString().Length - 2) + "};",
true);
}
It sounds like your issue is that you are using the master page for something it wasn't intended. The master page is a control just like any other control, and therefore cannot access any of the controls of its parent (the page). More info:
ASP.Net 2.0 - Master Pages: Tips, Tricks, and Traps
My suggestion is to inject the JavaScript from your page where the controls can actually be resolved. Here is a sample of how this can be done:
#Region " LoadJavaScript "
Private Sub LoadJavaScript()
Dim sb As New StringBuilder
'Build the JavaScript here...
sb.AppendFormat(" ctl = getObjectById('{0});", Me.asd.ClientID)
sb.AppendLine(" ctl.className = 'MyClass';")
'This line adds the javascript to the page including the script tags.
Page.ClientScript.RegisterClientScriptBlock(Me.GetType, "MyName", sb.ToString, True)
'Alternatively, you can add the code directly to the header, but
'you will need to add your own script tags to the StringBuilder before
'running this line. This works even if the header is in a Master Page.
'Page.Header.Controls.Add(New LiteralControl(sb.ToString))
End Sub
#End Region
I am writing a bit of code to add a link tag to the head tag in the code behind... i.e.
HtmlGenericControl css = new HtmlGenericControl("link");
css.Attributes["rel"] = "Stylesheet";
css.Attributes["type"] = "text/css";
css.Attributes["href"] = String.Format("/Assets/CSS/{0}", cssFile);
to try and achieve something like...
<link rel="Stylesheet" type="text/css" href="/CSS/Blah.css" />
I am using the HtmlGenericControl to achieve this... the issue I am having is that the control ultimatly gets rendered as...
<link rel="Stylesheet" type="text/css" href="/CSS/Blah.css"></link>
I cant seem to find what I am missing to not render the additional </link>, I assumed it should be a property on the object.
Am I missing something or is this just not possible with this control?
I think you'd have to derive from HtmlGenericControl, and override the Render method.
You'll then be able to write out the "/>" yourself (or you can use HtmlTextWriter's SelfClosingTagEnd constant).
Edit: Here's an example (in VB)
While trying to write a workaround for umbraco.library:RegisterStyleSheetFile(string key, string url) I ended up with the same question as the OP and found the following.
According to the specs, the link tag is a void element. It cannot have any content, but can be self closing. The W3C validator did not validate <link></link> as correct html5.
Apparently
HtmlGenericControl css = new HtmlGenericControl("link");
is rendered by default as <link></link>. Using the specific control for the link tag solved my problem:
HtmlLink css = new HtmlLink();
It produces the mark-up <link/> which was validated as correct xhtml and html5.
In addition to link, System.Web.UI.HtmlControls contains classes for other void element controls, such as img, input and meta.
Alternatively you can use Page.ParseControl(string), which gives you a control with the same contents as the string you pass.
I'm actually doing this exact same thing in my current project. Of course it requires a reference to the current page, (the handler), but that shouldn't pose any problems.
The only caveat in this method, as I see it, is that you don't get any "OO"-approach for creating your control (eg. control.Attributes.Add("href", theValue") etc.)
I just created a solution for this, based on Ragaraths comments in another forum:
http://forums.asp.net/p/1537143/3737667.aspx
Override the HtmlGenericControl with this
protected override void Render(HtmlTextWriter writer)
{
if (this.Controls.Count > 0)
base.Render(writer); // render in normal way
else
{
writer.Write(HtmlTextWriter.TagLeftChar + this.TagName); // render opening tag
Attributes.Render(writer); // Add the attributes.
writer.Write(HtmlTextWriter.SelfClosingTagEnd); // render closing tag
}
writer.Write(Environment.NewLine); // make it one per line
}
The slightly hacky way.
Put the control inside a PlaceHolder element.
In the code behind hijack the render method of the PlaceHolder.
Render the PlaceHolders content exactly as you wish.
This is page / control specific and does not require any overrides. So it has minimal impact on the rest of your system.
<asp:PlaceHolder ID="myPlaceHolder" runat="server">
<hr id="someElement" runat="server" />
</asp:PlaceHolder>
protected void Page_Init(object sender, EventArgs e)
{
myPlaceHolder.SetRenderMethodDelegate(ClosingRenderMethod);
}
protected void ClosingRenderMethod(HtmlTextWriter output, Control container)
{
var voidTags = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) { "br", "hr", "link", "img" };
foreach (Control child in container.Controls)
{
var generic = child as HtmlGenericControl;
if (generic != null && voidTags.Contains(generic.TagName))
{
output.WriteBeginTag(generic.TagName);
output.WriteAttribute("id", generic.ClientID);
generic.Attributes.Render(output);
output.Write(HtmlTextWriter.SelfClosingTagEnd);
}
else
{
child.RenderControl(output);
}
}
}