This question got me thinking about how one might go about doing the relatively undoable: seamlessly integrate server-generated HTML from ASP.NET with client-side control via javascript. Sure, you can always use javascript/jquery/libraries to create the same display client-side. But much of the time, it's easier to do all the rendering on the server instead of just passing data to a client-side control which must handle the user interface and rendering. Or maybe you've already got a lot of less-interactive server code that you'd really rather not completely re-do using javascript libraries to just add some better interactivity.
I had a theory which seems to work in a basic proof of concept. Suppose you want to completely re-render the HTML of a server-generated control based on a client-side event, without posting back. So using jquery, I have a page:
default.aspx:
<a id="link1" href="#">Click Here</a>
<div id="container">
<asp:PlaceHolder id="MyPlaceholder" runat="server" />
</div>
<script type="text/javascript">
$(document).ready(function() {
$('#link1').click(function() {
$.ajax({
url: "default.aspx",
type: "GET",
dataType: "html",
async: true,
data: { "ajax": "1" },
success: function(obj) {
// replace the HTML
$('#container').html(obj);
}
});
});
});
The event causes it to query itself with ajax. The codebehind that does the trickery is like this:
TestUserControl ctl;
string ajax;
protected void Page_Load(object sender, EventArgs e)
{
ctl = (TestUserControl)Page.LoadControl("TestUserControl.ascx");
Myplaceholder.Controls.Add(ctl);
ctl.OnRender += new TestuserControl.RenderHandler(ctl_Render);
}
protected void Page_PreRender()
{
ajax = Request.QueryString["ajax"] == null ? "" : Request.QueryString["ajax"];
}
void ctl_Render()
{
if (ajax == "1")
{
StringBuilder sb = new StringBuilder();
StringWriter sw = new StringWriter(sb);
using (HtmlTextWriter writer = new HtmlTextWriter(sw))
{
ctl.DoRender(writer);
}
Response.Write(sb.ToString());
Response.End();
}
}
In TestUserControl, i expose base.render to get the output:
public void DoRender(HtmlTextWriter writer)
{
base.Render(writer);
}
Basically, if the page is called without the "ajax" querystring, it just acts like itself. But when that querystring is used, it intercepts the output stream from the content I am concerned with (a usercontrol called TestUserControl.ascx) and renders just that. This is returned to the client, which updates the HTML. All the IDs will be recreated exactly as before since I am not trying to render just that control in isolation, but in context of its own page. Theoretically, every bit of magic created by ASP.NET should be reproduced, retrieved and updated by the ajax query.
Apart from the obvious lack of efficiency, it seems to work swimmingly in this little test. I can completely rerender the control using the server generated HTML without a postback and I've written zero javascript. This example doesn't actually change anything, but it would be simple to pass more parameters to change the output.
I was wondering if anyone had tried anything like this in practice? What potential problems might I not be thinking of?
If server performance is not an issue, it seems like it might be quite easy way to get a heck of a lot of functionality with all the benefits of ASP.NET server controls. But I can't seem to find any discussion of using this technique in practice so I am wondering what I might be missing.
Well, for starters, you're sending a GET request to your page, so the control you want to update won't receive up-to-date form data. More importantly, ViewState will be lost and you probably don't want that, unless your user control is very simple.
You could work around the problem by using a POST request, but there are other potential issues, e.g. can you guarantee that the client scripts embedded in your user control either run again or don't when you update it, consistently, on all browsers?
Fortunately, others have already solved those problems. I'd suggest you place your user control inside an UpdatePanel and force a refresh from the client side:
__doPostBack("yourUpdatePanelClientID", "");
If the alternative is using an UpdatePanel, it's definitely worthwhile to consider partial rendering. Even if you're only updating a small portion of a page with the UpdatePanel, these updates must send the entire ViewState back to the server, the server must run the entire page through its life cycle, render it, and then extract the partial area. You take a significant performance hit for its convenience.
In your case, you're still incurring a page life cycle hit since you're rendering that partial within an ASPX pae. It's not as bad as the UpdatePanel, but unnecessary.
I've found that splitting the partial rendering out to a web service or HttpHandler handler works well. It's much faster than most other methods for rendering partials in WebForms, but still allows the flexibility of using User Controls for templating.
The drawback is that controls inside the User Control cannot handle PostBacks. You can definitely re-render it and/or pass parameters in to control its rendering, but you can't use this to render a GridView and expect its paging links to work, for example.
I wish there was more discussion about the topic of moving ASP.NET web forms into the modern era. I've had to figure out most everything on my own. I personally strive for the simplest solutions, and avoid all solutions that require layering on even more Microsoft. Nothing against MS, I just want to keep it simple and use ordinary web standards.
So far, I've been able to AJAX down everything I've tried, using jQuery load. There are no pat, simple answers, but if you are persistent, you can probably solve any client side problem resulting from old school ASP.NET practices.
I think the number one most important thing to do is stop using ViewState. Completely. This will force you out of many terrible practices that would cause you problems on the client. This is actually easier to do than you might think. And at that point, AJAXing stuff down will usually just work. Even DataGrids. Do your own paging, whether client- or server-side, it's not that hard, and then you can reuse your solution everywhere.
The problems will come from third party stuff that you have no control over. In those cases you can make use of IE developer tools (F12) to see what ASP.NET was originally sending down. Worst case, you'll have to scrape out some JavaScript and run it yourself. In practice I rarely have to do anything that terrible. I suppose if you are using a lot of heavy weight controls this would be impractical, but in that case you've already bought the farm.
Related
I need to get a JavaScript variable from my code behind without doing a page refresh or a button click event. Here's my code:
aspx:
<asp:HiddenField ID="docLengthValue" runat="server" />
<script type="text/javascript">
var body = document.body,
html = document.documentElement;
var height = Math.max(body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight);
//alert(height + ": Page length");
document.getElementById("<%=docLengthValue.ClientID%>").setAttribute("Value", height);
</script>
c#:
//Skrollr body tag background parallax animation
string docLengthVar = docLengthValue.Value;
HtmlGenericControl skrollr = (HtmlGenericControl)this.Page.Master.FindControl("bodyBG");
skrollr.Attributes.Add("data-0", "background-position: 0px -120px;");
skrollr.Attributes.Add("data-" + docLengthVar, "background-position: 0px 0px;");
dataAttb.Text = "This is the Document length: " + docLengthVar;
How can I access the Value field of the <asp:HiddenField ID="docLengthValue" runat="server" />? I know that the JavaScript compiles after the C#, but is there a way to get this variable?
Jeff-
This is not really going to be an answer, and I apologize but I think you may be quite far off course here.
Your recent post history seems to indicate that you want a parallax effect on your web page. The posts I am referencing: 1 and 2.
Have you followed this tutorial? Where along it have you had a problem?
General
I think you have some confusion about how the your web pages are working. WebForms is a ASP technology, or active server page. You can read more about this but basically it provides a platform which you can more easily develop responsive web sites.
The JS you are writing is a technology that runs entirely in the browser. When the JavaScript runs the code has no idea that it is in an ASPX page. It has no idea how to talk to the code behind.
When your page renders it is pure and raw html that the client receives. Through the magic of the platform when the user submits the form or clicks a button your code behind runs. But that is nothing you can't do on your own. All that is happening is your browser is sending properly formatted HTTP requests, and your web server is dispatching those to pages (code / class(es).
Relation to your problem
You are trying to animate a background image on a web page. That is an entirely client side job. You do not want the client to be talking to the web server frequently enough to create a smooth transition. In reality it is likely impossible.
The stuff you are looking at is pure JavaScript and runs entirely in the browser. I highly encourage you to pursue that as the avenue to solve your problem. And then ask questions as you have problems with that.
Edit: Apologies if I have inferred too much. My suggestions are in earnest.
I have a MasterPage which will appear in every page in the application and I'm trying to load a "LoginBox" which uses PageMethods inside a Div tag in this MasterPage
So far I have tried doing as I would do on a Content Page, tried converting it into a User Control and tried using a server side include (< !--#include file="LoginBox.aspx"-->)
None succeeded.
I can see with firebug that the webresources get loaded but the PageMethods javascript isn't created in any of those methods.
I am REALLY trying to avoid having to create a WebService for this, and moving the LoginBox is not an option, I would rather drop the MasterPage idea, but then maintenance would become hell.
I need ideas or a direction on this.
Any help is appreciated
I got it working successfully with an iframe loaded from javascript, to me it's an ugly solution, but still one. I'm open for better solutions
<script type="text/javascript">
(function () {
var e = document.createElement('iframe');
e.setAttribute("src", "LoginBox.aspx");
e.setAttribute("scrolling", "no");
e.setAttribute("frameborder", "0");
e.setAttribute("height", "73px");
e.setAttribute("width", "225px");
e.setAttribute("marginheight", "0px");
e.setAttribute("marginwidth", "0px");
e.async = true;
document.getElementById('loginboxd').appendChild(e);
} ());
</script>
Looks to me like you're mashing classic asp with ASP.NET
the point of user controls is to encapsulate exactly what you are doing here.
even then however you will find your attempts to componentize your code will still lead to a messy mess mess. consider moving over to ASP.NET MVC if you can. with that you can do far more suitable and cleaner things to keep your codebase clean.
I have a web application I'm working on, and I'd like to avoid the clutter of if/elseif/else in the view, and of having way too many separate ascx files just for conditional inclusion in a view.
What I want to do is create some sort of simple class that works like so (an example will demonstrate what I'd like to achieve)
<% using(RequiresAccessTo.Feature(Features.FancyStuff)) { %>
Special content for users
<% } %>
If the user does not have access to the feature, the class would render a sign up link instead. I know I could simply use if/else, but the content of the else block could be one of 2-3 different standard responses depending on access level, and this mechanism would be used in countless places around the website.
If there a way to simply prevent Special content for users from rendering altogether, it'll mean I can make the templates around the website really easy to maintain.
Another option you might try would be to create a custom server control. Properties on the control could include the feature set you'd want to check permission for. If that permission wasn't met, the control would render the sign up link appropriate for that access level or permission. Your view would end up looking something like:
<controls:SignUpWrapper runat="server" id="signup" access="FancyStuff">
<div>
Approved user contents.
</div>
</controls:SignUpWrapper>
In your control, you would first check permission then render either the appropriate link or the provided HTML. The trickiest bit here might be getting the routing information to your server control code. Not something I've tried. Worst case scenario I imagine you should be able to pass the necessary information or even the entire sign up link through properties. No wait, worse would be bypassing routing altogether and forcing the URL in through a configuration value, erm... yeah. Either way it's a bit wordier than your desired example, but not far off.
I suppose some folk might see even the server control idea as a bit wonky. But as long as you stay away from view state, post back behavior and maybe a few other classic ASP.NET features, there's nothing preventing using server controls. We all use masters and content containers already. Sorry to preach if you're already in the choir. =)
For the time being, this is stretching my imagination and maybe even common sense a bit depending on the difficulty of generating that link. I'll check back if I think of anything else.
I can think of one other decent option to keeping your if/else logic in a partial view.
You could create an HtmlHelper extension method. HtmlHelper is the object used when calling things like Html.ActionLink in a view. You can write your own method that produces whatever HTML you want. The conditionals all take place in the extension method and your view code is reduced to:
<%= Html.MyControl(param1, param2) %>
The rule of thumb I follow when deciding when to create an HtmlHelper extension method and when to create a partial view is generally how much HTML is going to be generated. If I end up with more than a few lines of rendered HTML, a partial control is generally your best bet as it is generally easier to understand and maintain the HTML.
If you're worried about organizing numerous partial views, you can create subfolders under your Shared view directory. The views can then be referenced like this:
<% Html.RenderPartial("Subfolder/PartialView") %>
I just thought of an alternative solution:
<% if(!BlockContentAndRenderPlaceHolder(Feature.Whatever)) { %>
whatever
<% } %>
I know it looks a bit obtuse, but if you saw the content on these pages, you'd understand.
Or any viable workaround.
So, imagine a Master page that implements IFooMaster with a method ShowFancyMessagePopupTheBusinessCantLiveWithout(string message);
I am in a page that inherits this master page. Upon a checkbox being unchecekd, I want to show a message to the user that if they save it, they can't re-check the checkbox without some admin action.
I've been given feedback that I can't just use an alert('message'); in javascript because they want the consistent look of these messages.
Next, I tried to make an ajax call via PageMethods (as that's what everything else in this codebase uses) to show a message. My problem lies in this method being static.
[WebMethod]
public static void ShowSuperImportantMessage()
{
if(!checkboxICareAbout.Checked)
((IFooMaster)Master).ShowFancyMessagePopupTheBusinessCantLiveWithout("If you uncheck that thing, you can't recheck it.");
}
Since ShowSuperImportantMessage() is static, I can't access Master from within.
The method on the master page looks more or less like this:
public void ShowFancyMessagePopupTheBusinessCantLiveWithout(string message)
{
lblGenericMessage.Text = message;
btnGenericMessageOK.Focus();
upGenericMessage.Update();
mpeGenericMessage.Show();
}
mpeGenericMessage is an ajaxtoolkit:ModalPopupExtender.
upGenericMessage is an update panel.
The other 2 are obvious.
Any ideas? Can I do some jQuery kung-fu to show that stuff? I tried, but the solution complained that the controls I tried to refer to by ClientID didn't resolve since they were on the Master page.
quick edit: Before anyone tells me the architecture is a problem, or I shouldn't have put such a thing on a master page, or w/e...
I know the situation is not ideal, but I this is inherited code, and I can't drop it all and rewrite half of their web stack.
Try something like this (untested):
((IFooMaster) ((Page)HttpContext.Current.Handler).Master)
It appears this doesn't work - Master isn't hooked up when the PageMethod is called (makes sense).
So, instead, create an empty page using the same master page. Have that page accept either a POST or GET with whatever parameters you need to pass to your master-page method. Have the Page_Load extract the parameters and call the method. It should then use Response.Write to return a result (and remember to change the Content-Type). Have your client-side code call the page and get the result.
Did you try something like window.top before the ClientID?
Per comments
You don't need to hardcode ClientID. Since your js is in page, try something along the following lines....
window.top.document.getElementById( "<%= yourelement.ClientID %>" ).Whatever();
Sorry to take so long to respond/answer.
I'm not proud of this at all, mind you, but the eventual solution was to hardcode the client IDs into the jQuery that pulled up the modal dialog on the master page.
Like I said, I'm not proud of this dirty, dirty fix. However, the consolation is that, since it's on the master page, there isn't really any naming container above it. As such, it's much less likely to run into problems with the clientID changing.
Simple one here... is there a clean way of preventing a user from double-clicking a button in a web form and thus causing duplicate events to fire?
If I had a comment form for example and the user types in "this is my comment" and clicks submit, the comment is shown below... however if they double-click, triple-click or just go nuts on the keyboard they can cause multiple versions to be posted.
Client-side I could quite easily disable the button onclick - but I prefer server-side solutions to things like this :)
Is there a postback timeout per viewstate that can be set for example?
Thanks
I dont think that you should be loading the server for trivial tasks like these. You could try some thing jquery UI blocking solution like this one. Microsoft Ajax toolkit should also have some control which does the same. I had used it a long time ago, cant seem to recall the control name though.
With jQuery you can make use of the one event.
Another interesting read is this: Build Your ASP.NET Pages on a Richer Bedrock.
Set a session variable when the user enters the page like Session["FormXYZSubmitted"]=false.
When the form is submitted check that variable like
if((bool) Session["FormXYZSubmitted"] == false) {
// save to db
Session["FormXYZSubmitted"] = true;
}
Client side can be tricky if you are using Asp.Net validation.
If you have a master page, put this in the master page:
void IterateThroughControls(Control parent)
{
foreach (Control SelectedButton in parent.Controls)
{
if (SelectedButton is Button)
{
((Button)SelectedButton).Attributes.Add("onclick", " this.disabled = true; " + Page.ClientScript.GetPostBackEventReference(((Button)SelectedButton), null) + ";");
}
if (SelectedButton.Controls.Count > 0)
{
IterateThroughControls(SelectedButton);
}
}
}
Then add this to the master page Page_Load:
IterateThroughControls(this);
I have had the same scenario. The solution of one of my coworkers was to implement a kind of Timer in Javascript, to avoid considering the second click as a click.
Hope that helps,
Disable the button on click, utilize jquery or microsoft ajax toolkit.
Depending on how important this is to you, could create an array of one time GUID's which you remove from the array once the update has been processed (ie posted back in viewstate/hidden field)
If the guid is not in the array on postback, the request is invalid.
Substitute database table for array in a clustered environment.