Header.Stylesheet.CreateStyleRule on partial postback - asp.net

I've recently added an UpdatePanel to a single webpage which renders different usercontrols on button clicks. I'm struggling with a problem where i'm adding inline styling into the header dynamically with Header.Stylesheet.CreateStyleRule - the problem is that it doesn't get inserted on partial postbacks and it does make sense. This is obviously because it's a partial postback and the header is only being rendered when the page loads the first time.
The styling is being added by the following methods, which gets the styling from a database:
Private Sub InitializeStylesheet(ByVal ButtonId As Integer)
Dim CSSStylesheet As Stylesheet = StyleBackend.GetStylesheets(ButtonId)
Dim IdClassList As List(Of StyleIDClass) = StyleBackend.GetStylesheetsStyleIDClass(CSSStylesheet.StylesheetID)
'Iterates through the cssidclass
For Each cssidClass In IdClassList
Dim styleItems As List(Of StyleItem) = StyleBackend.GetStyleItemsByIDorClass(cssidClass.StyleIDClassID)
Dim cssString As String = Nothing
For Each StyleItem As StyleItem In styleItems
cssString += StyleItem.Property & ":" & StyleItem.Value & ";"
Next
'Iterates all cssitems which belongs to a cssidclass
Me.Header.StyleSheet.CreateStyleRule(New CustomStyle(cssString), Nothing, cssidClass.ClassOrID)
Me.Header.ViewStateMode = UI.ViewStateMode.Disabled
Next
End Sub
I've been googling my ass off trying to get this to work, but it seems to be impossible.
This msdn article literal says that it is impossible to do what i'm trying to do.
http://msdn.microsoft.com/en-us/library/system.web.ui.htmlcontrols.htmlhead.stylesheet.aspx
Adding styles or style rules programmatically during asynchronous postbacks is not supported. When you add AJAX capabilities to an ASP.NET Web page, asynchronous postbacks update regions of the page without updating the whole page. For more information, see Microsoft Ajax Overview.
Does anyone have a suggestion or alternative way of doing this?
Thank you!

One solution is just to include the styles in your main stylesheet(s) rather than modifying them dynamically. Even if they are data-driven, you could output them with the main page via an HTTP handler if the total list isn't too large.
Alternatively (and probably more efficiently), you can return the style data to the client as a string and process it there.
You can listen for the EndRequest event on the client script manager:
function endRequest(){
// look at the updated DOM
}
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequest);
Perhaps the following sequence:
Initiate async postback
Render the styles to a hidden field inside the UpdatePanel
On endRequest, examine the DOM to see if it contains a hidden field with styles (based on naming convention, data attributes, or just ID)
Grab the values out of the hidden field and add them to the page's style rules using JavaScript

Related

Passing Angular data to code-behind function on button click

I have a code-behind function that is called when a hyperlink is clicked. The function should then get the name attribute from the hyperlink and use that string to call another function. The name attribute is being populated by an angular ngRepeat. My problem is that when I try to get the name attribute in the code-behind, it just shows an empty string and not the name attribute that "should" have been passed.
HTML
Print Invoice
Final HTML Output
<a data-name="90979157" href="javascript:__doPostBack('ctl00$MainContentPlaceholder$ctl00','')">Print Invoice</a>
VB Code-Behind
Public Sub btnPrintInvoiceClicked(ByVal sender As Object, ByVal e As EventArgs)
Dim inv As HtmlAnchor = CType(sender, HtmlAnchor)
Dim invoiceNumber As String = inv.Name
GetInvoicePDF(invoiceNumber)
End Sub
Thanks in advance!
Edit: Added HTML output
As you can see from the HTML that gets sent to the browser, .NET causes the <a> tag to perform the javascript function __doPostBack('ctl00$MainContentPlaceholder$ctl00','') when it is clicked. This function submits the <form> tag which surrounds the entire contents of your page back to your code-behind class. That code-behind then does special magic with the hidden form elements on your page that .NET webforms put there to identify the state of the page.
This is the classic .NET webforms paradigm, and is a lot more complex than the <a> tag pointing to a url would have you believe. It is also very different from how angularjs is meant to be used. You said in a comment that the data-name value is correctly passed when hard-coded in. This is probably the doing of the __doPostBack function, and it's unsurprising that it does not play well with angular (since its generated per page by code that was written before angular was created). You have two possible options:
Change your <a> tag to an <input type="button" value="{{invoice.DocumentNumber}}" runat="server" ID="yourButton" /> Why would this work? Simply put, you just want angular to transclude the right value into the html so that when webforms submits, the data is passed to your page. Since <a> tags have no relationship to form values, the page rightly ignores its data-name value. In fact I'm surprised that hardcoding the value works at all, because a form post should ignore things that are not form elements. It cannot ignore an <input> element, though.
Change your Public Sub btnPrintInvoiceClicked... method from being a server-side onclick handler to be its own endpoint. Take a look at http://www.asp.net/web-api or its older, web-forms-era cousin: http://www.codeproject.com/Articles/353260/ASP-NET-Advanced-Generic-Handler-ASHX
As an aside, this is the first time I've heard of someone trying to use angular with webforms, the latter being a bit of a dinosaur that Microsoft has hinted at abandoning and the former being in-vogue javascript MVC darling. I know it may not be possible, but you should consider moving to .NET MVC if possible since it is a much better backend environment in which to host your angularjs app.

Rendering of asp.net controls

I find it hard when using asp.net controls, to make proper css files because I don't know what the html output for the different controls end up as.
For example, if I want to add a div tag using a asp.net control, it's not easy to know what kind of control I can use.
Are there any documentation that shows for each asp.net control, what the rendered html for that control will be? I understand that some controls will probably change it's output due to how it's configured, but most controls will at least follow a pattern here.
The best would of course be a service on the web where you can put in the asp.net control definition and get the rendered html out.
Currently I have to put a control into my webform, run it and check the source in the browser, and if it's not the correct html tag, try another control and repeat. That get's tedious quite fast.
If you want to know to what html-controls a server-control is rendered, you could call RenderControl:
Dim myGridView as new GridView
Dim sb as New StringBuilder()
Dim sw as New IO.StringWriter(sb)
Dim textWriter as New HtmlTextWriter(sw)
myGridView.RenderControl(textWriter)
' now we can have a look what asp.net has rendered: '
Dim gridViewHTML as String = sb.ToString()
The rendered html will even differ from browser to browser for example when ASP.Net thinks the client uses a "lower"-browser(BrowserCaps), a Panel will be rendered as Table instead of a DIV.
By the way, if you're testing my above code on controls inside of your page, you have to override VerifyRenderingInServerForm otherwise you get a "...must be placed inside a form tag with runat=server"-error:
Public Overrides Sub VerifyRenderingInServerForm(ByVal control As System.Web.UI.Control)
Return
End Sub
I would recommend adding a CssClass to your WebControls, and doing all your styling using classes, rather than HTML element types. As Tim Schmelter says, the html can render differently for different clients (I seem to remember a Panel can be a span as well under certain circumstances).
To avoid actually having to add the CssClass each time, you can subclass the WebControl you want, then set it's CssClass in Control_Init.

At what asp.net page event are clientIDs assigned?

I want to do something like this:
Panel divPanel = new Panel();
divPanel.ID = "divPanel";
this.Page.Controls.Add(divPanel);
string script = "function alertID(){alert("the id is: "+divPanel.ClientID+");}";
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "scripttests", script);
But when I put this code in page_load, I don't get the full id, which is ctl00_ContentMain_divPanel, I just get divPanel.
Is there another event I should use? How do I make this work?
PreRender is fine, however it should work in Page_Load as well.
http://msdn.microsoft.com/en-us/library/system.web.ui.control.clientid(VS.71).aspx
Is your script rendering code running in page_load as well? They are together in your example above, but perhaps they were just copy/pasted together?
If this code is in a custom control, you also need to add the INamingContainer interface to the control declaration for ClientID to work as you expect:
http://msdn.microsoft.com/en-us/library/system.web.ui.inamingcontainer.aspx
Additionally, if this is a custom control, you should add to the Controls collection of the control itself, not the containing page. ie: this.Controls.Add() instead of this.Page.Controls.Add()
In your example you are adding directly to the Page controls collection. This would mean that the client id should be divPanel. Perhaps the client id you expect it to be is the incorrect one.

Prevent asp.net generating someValidator.display = "Dynamic";

My asp.net page dynamically displays 207 questions (I can't control this). Each question have 10 validators. When ASP renders the page it creates following three lines for each validator:
var qsnQuestionSet_adult_qcQuestion_1_vldMaxAnswersValidator = document.all ? document.all["qsnQuestionSet_adult_qcQuestion_1_vldMaxAnswersValidator"] : document.getElementById("qsnQuestionSet_adult_qcQuestion_1_vldMaxAnswersValidator");
qsnQuestionSet_adult_qcQuestion_1_vldMaxAnswersValidator.display = "Dynamic";
qsnQuestionSet_adult_qcQuestion_1_vldMaxAnswersValidator.evaluationfunction = "CustomValidatorEvaluateIsValid";
Althrough these three lines are just 4kb you can imagine that 4*10*207 is quite a lot. How can I mark all validators as dynamic and set evaluationfunction to same value without asp generating the line for me?
This code is generated automatically by ASP.NET when the EnableClientScript option is set to true. As far as I'm aware the only way to get rid of it would be to set this to false however the obvious drawback is the validation will only happen on the server side during a postback.
To get around this you could tie the custom javascript validation function to the related control event such as a textbox onBlur event but without knowing more detail about what values you are trying to validate it is difficult to speculate further as to whether this could be a solution.
This javascript is rendered to the client by the AddAttributesToRender() method of the BaseValidator & CustomValidator classes from the System.Web.UI.WebControls namespace. Take a look at them in Reflector.
Protected Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter)
...
If (enumValue <> ValidatorDisplay.Static) Then
Me.AddExpandoAttribute(writer2, clientID, "display", PropertyConverter.EnumToString(GetType(ValidatorDisplay), enumValue), False)
End If
...
MyBase.AddExpandoAttribute(writer2, clientID, "evaluationfunction", "CustomValidatorEvaluateIsValid", False)
End Sub
You could write your own class to replace CustomValidator and change the way that it renders.
But, in this case I think it would be better to write your own javascript to handle validation and not use the validator controls.
P.S. If you're worried about the size of the HTML the first thing you should do is enable gzip compression on your IIS server.

Using AJAX callbacks with ASP.NET User controls

What's the best way to implement user controls that require AJAX callbacks?
I want to accomplish a few things:
Have events done in the browser (eg, drag and drop) trigger an AJAX notification that can raise a control event, which causes code on the page using the control to do whatever it needs to do (eg, change a value in a database).
Have partial updates (NOT using an updatepanel) that can do things like populate an auto-complete dropdown underneath a textbox.
Implement a single user control that is generic enough to be reused on several pages
Avoid having to implement logic on the page itself that passes events back to the control, because that is repetitive and hard to maintain
I'm using jQuery for most of the client side stuff, but for the actual AJAX calls I don't really care if it's jQuery or the ASP AJAX libraries.
Effectively what would be perfect is PageMethods on the user control, that would be easily callable from client-side script. Unfortunately, as far as I'm aware, pagemethods do not work on user controls.
I'll use an autocomplete control as an example:
I should be able to put the autocomplete control on the page, and then in the page code, have eg:
Public Sub HandleLookup(ByVal input As String, ByRef list As List(Of String) Handles MyControl.LookupEntries
list = New List(Of String)
' Query database for list of items..
For Each item as String in FullItemList
If item.StartsWith(input) then list.Add(item)
Next
Return list
End Sub
And do nothing else .. the rest of the code should be in the usercontrol.
Note, the controls I'm trying to make are much more specific than eg, autocomplete. They do not exist in any 3rd party libraries and I really need to be able to make them myself.
Look into implementing ICallbackEventHandler in your Page -- it's a simple way to make a call back to a page function from JavaScript.
Here's a good tutorial:
http://www.ajaxprojects.com/ajax/tutorialdetails.php?itemid=119
You might want to check out; Ra-Ajax UserControl Sample and combine that knowledge with Ra-Ajax Drag and Drop
Click the "Show code" C# icon to the left to see the usage of the code...

Resources