I have a page with a MultiView control, and some of the views are long enough to scroll. Since may of the controls in the reviews require postback to function properly, MaintainScrollPositionOnPostBack is enabled on the page.
I have a problem when the user transitions from one view to another. If they were at the bottom of a long view, and transition to another long view, the new view loads and is scrolled all the way to the bottom. I need to jump to the top of the page when the user goes to a new view within the MultiView.
I've tried using the OnActiveViewChanged event to:
- call RegisterStartupScript to set window.location.hash to an anchor that I placed at the top of the page.
- call RegisterStartupScript to call window.scrollTo(0,0)
- set MaintainScrollPositionOnPostBack to false temporarily
The problem is that none of these seem to affect the actual transition postback, they take effect on the next postback, which actually causes a bigger issue.
Anybody have a proven method to have a MultiView page jump to top of page only on postbacks that transition to a new view?
This is exactly the same problem I've been having today with a multiview.. I found your question and went looking for answers. Seems we found the same article!
(Article code in C#)
private void ResetScrollPosition()
{
if (!ClientScript.IsClientScriptBlockRegistered(this.GetType(), "CreateResetScrollPosition"))
{
System.Text.StringBuilder script = new System.Text.StringBuilder();
script.Append("function ResetScrollPosition() {");
script.Append(" var scrollX = document.getElementById(\'__SCROLLPOSITIONX\');");
script.Append(" var scrollY = document.getElementById(\'__SCROLLPOSITIONY\');");
script.Append(" if (scrollX && scrollY) {");
script.Append(" scrollX.value = 0;");
script.Append(" scrollY.value = 0;");
script.Append(" }");
script.Append("}");
//Create the ResetScrollPosition() function
ClientScript.RegisterClientScriptBlock(this.GetType(), "CreateResetScrollPosition",
script.ToString(), true);
//Add the call to the ResetScrollPosition() function
ClientScript.RegisterStartupScript(this.GetType(), "CallResetScrollPosition", "ResetScrollPosition();", true);
}
}
Found an answer/workaround, finally: 4Guys
You have to trick ASP.Net into doing it for you by manipulating the hidden fields it uses for tracking the scroll position.
Related
I have an UpdatePanel with a repeater in it that is re-bound after a user adds an item to it via a modal popup.
When they click the button to add a new row to the repeater the code-behind looks something like this:
protected void lbtnAddOption_Click(object sender, EventArgs e)
{
SelectedOption = new Option()
{
Account = txtAddOptionAccountNumber.Text,
Margin = chkAddOptionMargin.Checked,
Symbol = txtAddOptionSymbol.Text,
Usymbol = txtAddOptionUsymbol.Text,
};
Presenter.OnAddOption(); // Insert the new item
RefreshOptions(); // Pull down and re-bind all the items
mpeAddOptionDialog.Hide(); // Hide the modal
// ... Make call to jQuery scrollTo() method here?
}
This works fine and the new row will show up quickly via the UpdatePanel.
However, there are often hundreds of rows and where the new one is added is based on the current sorting column used.
So, I wanted to take this as a chance to use the sweet jQuery ScrollTo plugin. I know that if I give it the ID of my overflowed container div and the ID of an element within it, it will smoothly scroll straight to the users newly added row.
However, there are two problems:
I need to find the appropriate row so I can snag the ClientID for it.
I need to execute the jQuery snippet from my code-behind that will cause my newly updated repeater to scroll to the right row.
I've solved #1. I have a reliable method that will produce the newly added row's ClientID.
However, problem #2 is proving to be tricky. I know I can just call ScriptManager.RegisterStartupScript() form my code-behind and it will execute the JavaScript on my page.
The problem I'm having is that it seems that it is executing that piece of JavaScript before (I'm guessing) the newly refreshed DOM elements have fully loaded. So, even though I am passing in the appropriate jQuery line to scroll to the element I want, it is erroring out for me because it can't find that element yet.
Here is the line I'm using at the end of the method I posted above:
string clientID = getClientIdOfNewRow();
ScriptManager.RegisterStartupScript(this, typeof(Page), "ScrollScript", String.Format("$(\"#optionContainer\").scrollTo(\"{0}\", 800);", clientID), true);
What do I need to do so I can ensure that this line of JavaScript isn't called until the page with the UpdatePanel is truly ready?
If the stuff you need to process is in the update panel, then you need to run your JS once that panel is loaded. I use add_endRequest for that. This below is hacked from something rather more complex. It runs once on document ready, but installs the "end ajax" handler which is triggered every time your update panel is updated. And by the time it fires, it's all there for you.
var prm = Sys.WebForms.PageRequestManager.getInstance();
jQuery(document).ready(function () {
prm.add_endRequest(EndRequestHandler);
});
function EndRequestHandler(sender, args) {
// do whatever you need to do with the stuff in the update panel.
}
Obviously you can inject that from code-behind if you want.
You can use the Sys.Application.load event which is raised after all scripts have been loaded and the objects in the application have been created and initialized.
So your code would be:
string clientID = getClientIdOfNewRow();
ScriptManager.RegisterStartupScript(this, typeof(Page)
,"ScrollScript"
,String.Format("Sys.Application.add_load(function(){{$(\"#optionContainer\").scrollTo(\"{0}\", 800);}});"
, clientID)
, true);
Can I save a page position in browser after clicking button or some other actions (after PostBack)
I need it on Page Change event in my DBGrid like on msdn.microsoft.com - when I change Tab C# -> C++ for example it doesn't refresh whole page and I still being on same position in browser.
You can do it by specifying MaintainScrollPositionOnPostback="true" in the page directive of the ASPX file.
for example:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="MyPage.aspx.cs" Inherits="MySite.MyPage" MaintainScrollPositionOnPostback="true" %>
Enjoy,
Koby
Going back to my 1.1 projects, I have done this through a hidden text box and some JavaScript.
First, you need to track the position of the scroll bars on the page.
document.body.onscroll = function(){
document.getElementById('hiddentextboxid').value =
document.body.scrollLeft + '|' + document.body.scrollTop;
}
where hiddentextboxid is the ClientID of a hidden text box you placed on the page.
Second, you need to reset the scroll position when the page is again displayed. My first step in doing this was to emit a javascript function to reset the position:
string sPosition = HttpContext.Current.Request.Form[hiddentextboxid];
if (sPosition != null && sPosition != "")
{
string[] sPos = sPosition.Split("|".ToCharArray());
StringBuilder strScript = new StringBuilder();
strScript.Append("<script language='Javascript'>\r\n");
strScript.Append("function ProcessScroll(){\r\n");
strScript.Append(" window.scrollTo(" + sPos[0] + ", " + sPos[1] + ");\r\n");
strScript.Append("}\r\n");
strScript.Append("</script>");
if (!Page.ClientScript.IsClientScriptBlockRegistered(this.GetType(), "Scroll"))
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "Scroll", strScript.ToString());
}
I then had this chunk of code to call that javascript function when appropriate:
sScript.Append("<script language='javascript'>\r\n");
sScript.Append("document.body.onload = function() {\r\n");
if (Page.ClientScript.IsClientScriptBlockRegistered(this.GetType(), "Scroll"))
{
sScript.Append(" ProcessScroll();\r\n");
sScript.Append("}\r\n");
sScript.Append("</script>");
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "ProcessOnLoad", sScript.ToString());
}
I believe there are some newer and slightly more efficient ways to do portions of this. However, I haven't used this code in quite a while because I typically do Ajax postbacks now to avoid this problem.
I have the following JQuery code in a external JS file linked into a
usercontrol in .Net 1.1 webapp.
The usercontrol is a timesheet.
When the page loads it calls MonthChange and works fine in one page.
But now I want to load the timesheet/usercontrol into aother
webpage that pops up a in a new browser window for printing.
Problem is my MonthChange is not firing.
Any ideas why???
$(function() {
MonthChange();
//TestData();
$('[class^=TSGridTB]').blur(function() {
var day = GetDay($(this).attr('id'));
var date = GetRowDate(day);
var bgcolor = GetInputFieldColor(date, false);
$(this).css("background-color", bgcolor);
$(this).parent().css("background-color", bgcolor);
//CalcHours($(this).get(0));
});
$('[class^=TSGridTB]').focus(function() {
var day = GetDay($(this).attr('id'));
var date = GetRowDate(day);
var bgcolor = GetInputFieldColor(date, true);
$(this).css("background-color", bgcolor);
$(this).parent().css("background-color", bgcolor);
});
$('[id$=lstMonth]').change(function() {
MonthChange();
});
});
without seeing further code, ensure that the selector is correct for the control in the new page.
The problem may be that the DOM has changed for the new page/window and JQuery does not yet know about it.
The change event
fires when a control loses the input
focus and its value has been modified
since gaining focus.
You might want to use the live event:
Binds a handler to an event (like
click) for all current - and future -
matched element.
When you bind a "live" event it will
bind to all current and future
elements on the page (using event
delegation). For example if you bound
a live click to all "li" elements on
the page then added another li at a
later time - that click event would
continue to work for the new element
(this is not the case with bind which
must be re-bound on all new elements).
Did you make sure that the new web page has jQuery script includes?
ensure you're using:
$(document).ready(
);
around your entire code block. The $ alone often does not do the trick.
I have a dynamically created table and in some of the cells i have an image button associated with the redBall_Click() handler
(here is the code behind)
TableCell cellOK = new TableCell();
cellOK.Style.Add(HtmlTextWriterStyle.TextAlign, "Center");
cellOK.Width = new Unit("3%");
ImageButton redBall = new ImageButton();
redBall.CausesValidation = false;
redBall.ID = id;
redBall.ImageUrl = "~/App_Themes/DotRed.png";
redBall.Click += new ImageClickEventHandler(redBall_Click);
cellOK.Controls.Add(redBall);
My problem is that the redBall_Click() method is never called (neither after the PostBack)
How can i solve this?
P.S. : I can't use a static link because every ImageButton is associated with a specific ID that i must pass to the page i call (for example as a Session object)
You have to set up the event handler within OnInt and do it every time the page is created, even during a postback. Make sure you don't have the code within a block like:
if(!this.IsPostback)
{
...
}
Have you tried putting a breakpoint in the page load method before clicking the image? I suspect this might show that the postback is happening, even if the image click handler isn't getting fired.
Is the code to create the image button also invoked during a postback? If not, the page will postback, but the image button won't exist to invoke the click handler. Sounds bizarre, but it's caught me out a few times.
I finally resolved (just yesterday).
I guess I was building my buttons too late: in the Page_PreRender (after the event was to be fired) maybe if i put the creation in Page_Load (yes, I create everything also on PostBack) it would handle the click, but I think I won't try since I found a workaround.
I'll explain for anyone who could have the same problem:
private TableCell cellOK(string id)
{
TableCell cellOK = new TableCell();
cellOK.Style.Add(HtmlTextWriterStyle.TextAlign, "Center");
Image redBall = new Image();
redBall.ID = id; // useless
redBall.ImageUrl = "redball.gif";
HyperLink hl = new HyperLink();
hl.Controls.Add(redBall);
hl.NavigateUrl = MyPage + "?id=" + id;
cellOK.Controls.Add(hl);
return cellOK;
}
and in Page_Init()
string m_QueryStringId = Request.QueryString.Get("id");
if (!string.IsNullOrEmpty(m_QueryStringId))
{
// Do something
}
When an Event is triggered by a user in IE, it is set to the window.event object. The only way to see what triggered the event is by accessing the window.event object (as far as I know)
This causes a problem in ASP.NET validators if an event is triggered programmatically, like when triggering an event through jQuery. In this case, the window.event object stores the last user-triggered event.
When the onchange event is fired programmatically for a text box that has an ASP.NET validator attached to it, the validation breaks because it is looking at the element that fired last event, which is not the element the validator is for.
Does anyone know a way around this? It seems like a problem that is solvable, but from looking online, most people just find ways to ignore the problem instead of solving it.
To explain what I'm doing specifically:
I'm using a jQuery time picker plugin on a text box that also has 2 ASP.NET validators associated with it. When the time is changed, I'm using an update panel to post back to the server to do some things dynamically, so I need the onchange event to fire in order to trigger the postback for that text box.
The jQuery time picker operates by creating a hidden unordered list that is made visible when the text box is clicked. When one of the list items is clicked, the "change" event is fired programmatically for the text box through jQuery's change() method.
Because the trigger for the event was a list item, IE sees the list item as the source of the event, not the text box, like it should.
I'm not too concerned with this ASP.NET validator working as soon as the text box is changed, I just need the "change" event to be processed so my postback event is called for the text box. The problem is that the validator throws an exception in IE which stops any event from being triggered.
Firefox (and I assume other browsers) don't have this issue. Only IE due to the different event model. Has anyone encountered this and seen how to fix it?
I've found this problem reported several other places, but they offer no solutions:
jQuery's forum, with the jQuery UI Datepicker and an ASP.NET Validator
ASP.NET forums, bug with ValidatorOnChange() function
I had the same problem. Solved by using this function:
jQuery.fn.extend({
fire: function(evttype){
el = this.get(0);
if (document.createEvent) {
var evt = document.createEvent('HTMLEvents');
evt.initEvent(evttype, false, false);
el.dispatchEvent(evt);
} else if (document.createEventObject) {
el.fireEvent('on' + evttype);
}
return this;
}
});
So my "onSelect" event handler to datepicker looks like:
if ($.browser.msie) {
datepickerOptions = $.extend(datepickerOptions, {
onSelect: function(){
$(this).fire("change").blur();
}
});
}
I solved the issue with a patch:
window.ValidatorHookupEvent = function(control, eventType, body) {
$(control).bind(eventType.slice(2), new Function("event", body));
};
Update: I've submitted the issue to MS (link).
From what you're describing, this problem is likely a result of the unique event bubbling model that IE uses for JS.
My only real answer is to ditch the ASP.NET validators and use a jQuery form validation plugin instead. Then your textbox can just be a regular ASP Webforms control and when the contents change and a postback occures all is good. In addition you keep more client-side concerns seperated from the server code.
I've never had much luck mixing Webform Client controls (like the Form Validation controls) with external JS libraries like jQuery. I've found the better route is just to go with one or the other, but not to mix and match.
Not the answer you're probably looking for.
If you want to go with a jQuery form validation plugin concider this one jQuery Form Validation
Consider setting the hidden field _EVENTTARGET value before initiating the event with javascript. You'll need to set it to the server side id (replace underscore with $ in the client id) for the server to understand it. I do this on button clicks that I simulate so that the server side can determine which OnClick method to fire when the result gets posted back -- Ajax or not, doesn't really matter.
This is an endemic problem with jQuery datepickers and ASP validation controls.
As you are saying, the wrong element cross-triggers an ASP NET javascript validation routine, and then the M$ code throws an error because the triggering element in the routine is undefined.
I solved this one differently from anyone else I have seen - by deciding that M$ should have written their code more robustly, and hence redeclaring some of the M$ validator code to cope with the undefined element. Everything else I have seen is essentially a workaround on the jQuery side, and cuts possible functionality out (eg. using the click event instead of change).
The bit that fails is
for (i = 0; i < vals.length; i++) {
ValidatorValidate(vals[i], null, event);
}
which throws an error when it tries to get a length for the undefined 'vals'.
I just added
if (vals) {
for (i = 0; i < vals.length; i++) {
ValidatorValidate(vals[i], null, event);
}
}
and she's good to go. Final code, which redeclares the entire offending function, is below. I put it as a script include at the bottom of my master page or page.
Yes, this does break upwards compatibility if M$ decide to change their validator code in the future. But one would hope they'll fix it and then we can get rid of this patch altogether.
// Fix issue with datepicker and ASPNET validators: redeclare MS validator code with fix
function ValidatorOnChange(event) {
if (!event) {
event = window.event;
}
Page_InvalidControlToBeFocused = null;
var targetedControl;
if ((typeof (event.srcElement) != "undefined") && (event.srcElement != null)) {
targetedControl = event.srcElement;
}
else {
targetedControl = event.target;
}
var vals;
if (typeof (targetedControl.Validators) != "undefined") {
vals = targetedControl.Validators;
}
else {
if (targetedControl.tagName.toLowerCase() == "label") {
targetedControl = document.getElementById(targetedControl.htmlFor);
vals = targetedControl.Validators;
}
}
var i;
if (vals) {
for (i = 0; i < vals.length; i++) {
ValidatorValidate(vals[i], null, event);
}
}
ValidatorUpdateIsValid();
}
This is how I solved a simlar issue.
Wrote an onSelect() handler for the datepicker.
link text
In that function, called __doPostBack('textboxcontrolid','').
This triggered a partial postback for the textbox to the server, which called the validators in turn.