How do I stop blazor #onclick re-rendering page - .net-core

I am using a blazor sever app
I have a control that when clicked using the #onclick event handler I want to navigate to a new page using the NavigationManager in the click event method.
it doesn't really matter what the control is (button, a, tr, etc) they all have the same behavior
if I put a break point in the HTML I can see the current page is re-rendering before it goes to the new page.
a simple way to reproduce this behavior is to make a new blazor project and in the counter.razor page
change the code to this
#page "/counter"
<h1>Counter</h1>
#if (1 == 1)
{
<p>Current count: #currentCount</p>
}
<button class="btn btn-primary" #onclick="IncrementCount">Click me</button>
#code {
private void IncrementCount()
{
//currentCount++;
}
}
put a break point on the HTML line "#if (1 == 1)"
when the button is clicked it calls the click event which does nothing (code commented out), it then re-renders the page and the break point is hit.
The same happens if I add code in the click event calling navigationManager to navigate away from the page, it re-renders before it leaves the page when nothing has changed on the page.
adding onclick:preventDefault and/or #onclick:stopPropagation does not change this
the only thing I have found that does work is adding
private bool c_blnStopRendering = false;
protected override bool ShouldRender()
{
if (c_blnStopRendering == true) { return false; }
return base.ShouldRender();
}
and setting the c_blnStopRendering = true; in the click event
but this seems like over kill and very manual to add it everywhere it is needed

That's the only way to do it at the moment. There is a request in the backlog https://github.com/dotnet/aspnetcore/issues/18919

I was wondering how you got on with this. I had exactly the same problem (I just wanted to NavigateTo without any re-rendering). Overriding ShouldRender to return false, stopped everything - i.e. it didn't NavigateTo the URL. I was however, Navigating to the same page, but with different parameters in the QS. I seem to have solved the problem, (I think !) when I discovered an optional parameter in the NavigateTo method (public void NavigateTo(string uri, bool forceLoad = false);). I set that to true and it seems to be OK now. Was just interested in your experience

Related

DispatchKeyEvent stops firing after Xamarin Forms Entry control IsFocused

I am building a Xamarin Forms mobile app that runs in Android on a Zebra scanner. I flip 2 different StackLayouts to IsVisble true/false to display different stuff in the UI. (StackLayout1 and StackLayout2)
The customer wants the user to be able to use the app entirely from the hardware keyboard on the scanner. So I have used the device Settings so that it never displays the virtual keyboard (I don’t think that matters for the issue I am having.)
I am overriding DispatchKeyEvent in a PageRenderer in the Android project and everything is working great … except.
The problem case:
StackLayout1 is displayed
the user taps an Entry control, putting the focus there
the user taps a button in the UI
the app displays StackLayout2
at this point the DispatchKeyEvent never fires no matter what key I press on the device keyboard
If an Entry box does NOT get the focus (step #2 above) the DispatchKeyEvent always fires in StackLayout2 and the StackLayouts display as expected.
If I programatically put the focus in an Entry box in StackLayout2 at step #3 above the DispatchKeyEvent fires fine.
That is not an OK solution. I have tried to progamatically put the focus on StackLayout2, and that code seems to do what is expected but DispatchKeyEvent does not fire.
Maybe I need to do something in the Android-project PageRenderer so that it is aware of StackLayout2 when it is made IsVisible = true.
Update 2: I found that I did NOT need custom StackLayouts. The solution which I posted below does not include any of this stuff I am describing in Update 1 (sorry, if that's confusing).
Update 1:
I added a ViewRenderer for both StackLayouts, and the code is hitting the OnElementChanged event when StackLayout2's IsVisible property flips to true, just great. Although the problem case is the same: DispatchKeyEvent does not fire once StackLayout2 is displayed, if an EntryBox had the focus in StackLayout1
Here is the OnElementChanged part of the new StackLayout ViewRenders
async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "IsVisible":
if (Element.IsVisible)
{
if (sender is StackLayout)
{
this.FocusableViewAvailable(this); // if I comment these 2 lines out I get the same bad result
this.Focusable = true; // if I comment these 2 lines out I get the same bad result
this.FocusableInTouchMode = true;
var dd = this.RequestFocus(); // this is always false
var ee = this.IsFocused; // this is always false
}
}
break;
}
}
Also, as I am pointing out in the comments ^ there, IsFocused is always false.
Ideas?
My hunch, "Maybe I need to do something in the Android project PageRenderer" was correct. In the DispatchKeyEvent I had to make the MainPage have the focus when the keypress was handled.
Here is what the DispatchKeyEvent looks like now (notice the comments):
public override bool DispatchKeyEvent(KeyEvent ke)
{
// MainPage.ReceiveKeyPress(e); is the method that this method returns to
bool KeyPressWasHandled = false;
KeyPressWasHandled = (Element as MainPage).ReceiveKeyPress(ke);
if (KeyPressWasHandled)
{
// this next block seems to be needed so that this class
// continues to receive the keypress event after an Entry box has had the focus
this.Focusable = true;
this.FocusableInTouchMode = true;
this.RequestFocus();
return true; // returning true tells the parent class that the keypress has been handled
} else
{
try
{
return base.DispatchKeyEvent(ke);
}
Now the "problem case" in my initial post is no longer a problem.
NOTE: I found that I did NOT need the custom ViewRenderers that I had made for the StackLayouts.

Xamarin Forms AppShell How return to previous page

I see a lot of threads on this sort of subject but none seem to apply to my question. I don't have a navigation page, I have a hamburger menu -- so Push/PopAsync() would not appear to be the answer. And I don't want to go to a specific page, I want to go back to the previous page (whatever it was) so GoToAsync() would not appear to be the answer.
Xamarin app for Android and UWP with iOS somewhere in the future. The description of the problem below is specific to Android; it works a little differently on UWP.
I want to put a Help entry in the hamburger menu that will take the user to a help page in the default browser. Hamburger menu seems to only go to an app page, so I defined a "dummy" View page that displays "Loading ..." and issues Browser.OpenAsync() in its OnAppearing() method, and that pretty much works. The problem is that the user would expect that the Back button would take him or her to the page they were on before clicking Help. I tried a couple of things. I have gotten close with the following but it does not quite work correctly:
In each of my other Views' OnAppearing() I call a method that saves the value of Current.MainPage.CurrentItem in a static. Then in the Help page after the OpenAsync() I set Current.MainPage.CurrentItem to its setting from the last page before the Help page.
Console.WriteLine("#### HelpPage loading Web Help");
_ = State.DisplayHelpPage(this, "MainHelp"); // _ = await Browser.OpenAsync(uri, blo);
Console.WriteLine("#### HelpPage returning to previous page");
State.ReloadPreviousPage(); // Current.MainPage).CurrentItem = lastFlyoutItem;
It almost works. The first time I click Help in the hamburger menu I get
#### HelpPage loading Web Help
#### HelpPage returning to previous page
#### HelpPage loading Web Help
#### HelpPage returning to previous page
The Web page loads perfectly. But when I click the Back button it displays again. Obviously my OnAppearing() method has been driven twice, which I do not understand.
If I click the Back button again I come back to the previous page in the application just as I wanted. The next time I click Help in the Hamburger menu it takes me to my dummy View page with no Web page. Obviously, my OnAppearing() is not being driven at all. But after that it works perfectly. I can go to any app page, and click Help in the menu. I get the Web page, and the Back button takes me back to the app and the page. In UWP of course the browser does not load on top of the app Views, and I seem to see it being loaded twice every time.
So ... what should I be doing differently? And why is my OnAppearing() being driven twice and then not at all ... and thereafter as I would expect?
There are several parts to this answer:
Get the previous page on to the Navigation stack. This is done by intercepting the Route "//HelpPage", and replacing it with a route that ISN'T a child of Shell.
Remember "FakePageVisible", so we know to do "PopAsync" in OnResume, when app returns from browser.
(Optional) "Entering" flag prevents going to browser twice.
App.xaml.cs:
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
protected override void OnResume()
{
if (HelpPage.FakePageVisible) {
HelpPage.FakePageVisible = false;
var shell = MainPage as AppShell;
if (shell != null) {
shell.Navigation.PopAsync();
}
}
}
}
AppShell.xaml.cs:
public partial class AppShell : Xamarin.Forms.Shell
{
public AppShell()
{
InitializeComponent();
// Define a route that isn't a child of Shell.
Routing.RegisterRoute("Help2", typeof(HelpPage));
}
protected override void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
if (args.Current != null) {
if (args.Source == ShellNavigationSource.ShellItemChanged) {
if (args.Target.Location.OriginalString == "//HelpPage") {
// Cancel the original route.
args.Cancel();
Device.BeginInvokeOnMainThread(() => {
// Used by the next OnAppearing.
HelpPage.Entering = true;
// Go there by a route that isn't a child of Shell.
// Doing so, pushes our previous location on to Navigation stack.
Shell.Current.GoToAsync("Help2");
});
}
}
}
}
}
HelpPage.xaml.cs:
public partial class HelpPage : ContentPage
{
public static bool Entering;
public static bool FakePageVisible;
protected override void OnAppearing
{
// Make sure this only happens once (just in case).
if (Entering) {
Entering = false;
FakePageVisible = true;
Xamarin.Essentials.Browser.OpenAsync("https://aka.ms/xamarin-quickstart");
}
}
}
For a simple demo, this code communicates via static variables in HelpPage. Re-factor as appropriate for your situation.

asp.net mobile/desktop site toggle button, switching masterpage, but styles "stuck"

Summary
I'm having style issues when flipping master pages via a button event in asp.net 4.0. The new master switches, but the css from the old master remains. I don't understand how this could happen as the styles are defined within the head of the old master, and i can clearly see via the markup the new master is being displayed with whats supposed to be a totally different set of styles. Also, viewing source shows all the new css declarations in the head. How can i get this to "refresh" or "reload"?
Some details
I'm implementing a mobile version of my asp.net site. If a mobile device is detected i set a cookie and switch the master page in the preinit to a mobile friendly one. This works fine:
protected virtual void Page_PreInit(Object sender, EventArgs e)
{
if (IsMobile)
this.Page.MasterPageFile = "m-" + this.Page.MasterPageFile;
}
I have a "full site" button at the bottom that allows you to flip back and forth between the mobile and desktop view. When clicking it, i change the value in the cookie. Then when the page redirects to itself, the value is checked, and it gives the respective masterpage. This also "works", i can tell the right masterpage is rendering via markup. Except the styles from the mobile version remain even when the desktop master is being displayed. I did the redirect thinking it would prevent this.
// desktop/mobile site toggle button click event
protected void viewMobileButton_Click(Object sender, EventArgs e)
{
HttpCookie isMobileCookie = Cookies.snatchCookie("isMobile");
if (bool.Parse(isMobileCookie.Value))
Cookies.bakeCookie("isMobile", "false");
else
Cookies.bakeCookie("isMobile", "true");
Response.Redirect(Request.RawUrl);
}
This is the first time I've done anything like this, and not sure if i'm even going about it the right way, or how to debug from here. Thanks in advance for any help.
Edit
Ok, so i figured out it's related to the JQuery Mobile Scripts. JQuery Mobile has this way of tying pages together. I don't fully understand it, i think they use it for page transitions, and it's preventing my new CSS from registering. When i turn it off, my masterpage flips fine with css included. I'm looking into a way to turn off JQuery Mobile before my redirect. Note sure how though yet.
The problem ended up being related to JQuery Mobile AJAX for page-transitions. JQuery Mobile does not load the head of the document on additional page requests after the first.
So when i'd switch the mobile master to the desktop master, the head of the document wouldn't load to bring in my styles. There are a few way's this can be fixed:
This way just turns off AJAX altogether, and fixes the problem, but then you can't benefit from it:
<form data-ajax="false">
This is a way to do it problematically, but remind you, it will not work via an event after initialization of JQuery Mobile, so again you can't benefit from it:
$.mobile.ajaxEnabled = false;
The above two solutions i support could work if you redirected through a page first if you have to use an onclick event and an event handler.
A better solution is to add rel="external" to the link to tell JQM it's and outgoing link.
<a href="myself.com?mobile=true" rel="external" >
But because i couldn't run some code i wanted to in order to change the cookie, i had to pass a query string parameter, check it on the preinit, then set the cookie which my page also looks at on the preinit and flips the master.
Here's my full solution below in case someone is out there doing the exact same thing. Note because my website is using aliasing, i had to read Request.RawUrl and parse it myself since the Request.QueryString object did not contain the values i passed.
// reusable function that parses a string in standard query string format(foo=bar&dave=awesome) into a Dictionary collection of key/value pairs
// return the reference to the object, you have to assign it to a local un-instantiated name
// will accept a full url, or just a query string
protected Dictionary<string, string> parseQueryString(string url)
{
Dictionary<string, string> d = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(url))
{
// if the string is still a full url vs just the query string
if (url.Contains("?"))
{
string[] urlArray = url.Split('?');
url = urlArray[1]; // snip the non query string business away
}
string[] paramArray = url.Split('&');
foreach (string param in paramArray)
{
if (param.Contains("="))
{
int index = param.IndexOf('=');
d.Add(param.Substring(0, index), param.Substring(++index));
}
}
}
return d;
}
Then i just use my dictionary object to evaluate and rebuild my url with the opposite mobile value, dynamically setting the href on the toggle link. Some code is obviosuly left out, but for perspective, base._iPage.QueryStringParams hold my dictionary object that was returned, and base._iPage.IsMobile is just a bool property i also have via the page interface i use, that all my pages, and user controls, ect, can talk to.
// get the left side fo the url, without querystrings
StringBuilder url = new StringBuilder(Request.RawUrl.Split('?')[0]);
// build link to self, preserving query strings, except flipping mobile value
if (base._iPage.QueryStringParams.Count != 0)
{
if (base._iPage.QueryStringParams.ContainsKey("mobile"))
{
// set to opposite of current
base._iPage.QueryStringParams["mobile"] = (!base._iPage.IsMobile).ToString();
}
int count = 0;
url.Append('?');
// loop through query string params, and add them back on
foreach (KeyValuePair<string, string> item in base._iPage.QueryStringParams)
{
count++;
url.Append(item.Key + "=" + item.Value + (count == base._iPage.QueryStringParams.Count ? "" : "&" ));
}
}
// assign rebuild url to href of toggle link
viewMobileButton.HRef = url.ToString();
}
Then on my pageinit this is where i actually check, first the quesry string, then the cookie, if neither of those are present, i run my mobile detection method, and set a cookie, and my interface bool property for easy access to conditionals that depends on it.
QueryStringParams = base.parseQueryString(Request.RawUrl);
if (QueryStringParams.ContainsKey("mobile") ? QueryStringParams["mobile"].ToLower().Equals("true") : false)
{
Cookies.bakeCookie("isMobile", "true"); // create a cookie
IsMobile = true;
}
else if (QueryStringParams.ContainsKey("mobile") ? QueryStringParams["mobile"].ToLower().Equals("false") : false)
{
Cookies.bakeCookie("isMobile", "false"); // create a cookie
IsMobile = false;
}
else
{
IsMobile = base.mobileDetection();
}
if (IsMobile)
this.Page.MasterPageFile = "m-" + this.Page.MasterPageFile;
}

How to avoid duplicate entry from asp.net on Postback?

I have a dropdown list that pulls data from template table. I have an Add button to insert new template. Add button will brings up jQuery popup to insert new values. There will be a save button to save the new data. On_Save_Click I enter the new data and close the popup.
Here is the proplem:
When I refresh the page, the page entering the values again. So, I get duplicate entries!
Question:
How can I avoid this issue? I check out Satckoverflow and Google, both they suggest to redirect to another page. I don't want to redirect the user to another page. How can I use the same form to avoid this issue? Please help.
You can use viewstate or session to indicate if data already inserted (button pressed).
Something like this:
private void OnbuttonAdd_click()
{
if(ViewState["DataInserted"] != "1")
{
...
// Add new entry...
...
if(data inserted successfully)
{
ViewState["DataInserted"] = "1";
}
}
}
Edit:
public bool DataInserted
{
get
{
if (HttpContext.Current.Session["DataInserted"] == null)
{
HttpContext.Current.Session["DataInserted"] = false;
}
bool? dataInserted = HttpContext.Current.Session["DataInserted"] as bool?;
return dataInserted.Value;
}
set
{
HttpContext.Current.Session["DataInserted"] = value;
}
}
...
private void OnbuttonAdd_click()
{
if(!DataInserted)
{
...
// Add new entry...
...
if(data inserted successfully)
{
DataInserted = true;
}
}
}
The simplest way is to use a post/redirect/get pattern.
Basically, the refresh action for page build with post requires to repost the data. Using this pattern, you will reload the whole page.
With ASP.Net, you have a simple alternative, use an UpdatePanel. This will refresh only part of the page using AJAX. As the page itself is still the result of a GET request, you can refresh the page. And as you use ASP.Net, it's quite easy to integrate.
Finally, you can use a home made AJAX refresh. A combination of jQuery, KnockOut and rest services (for example), can help you to avoid refreshing the full page in benefits of an ajax call.
There is some experience:
Disable Submit button on click (in client side by JavaScript).
change Session['issaved'] = true on save operation at server side and change it on new action to false.
use view state for pass parameters like RecordId (instead of QueryString) to clear on refresh page. i always pass parameter's with Session to new page, then at page load set
ViewState['aaa']=Session['aaa'] and clear Sessions.
...I hope be useful...
Do this it is very easy and effective
Intead of giving IsPostBack in the page load(),please provide inside the button click (To send or insert data)
Call the same page again after reseting all input values
protected void Btn_Reg_Click1(object sender, EventArgs e)
{
try
{
if (IsPostBack)
{
Registration_Save();
Send_Mail();
txtEmail.Text = "";
txtname.Text = "";
Response.Redirect("~/index.aspx");
}
}
catch (Exception) { }
}
You won't see any server messages after refreshing the page..

ASP.NET Custom Button Control - How to Override OnClientClick But Preserve Existing Behaviour?

So i have a ASP.NET 4 Custom Control called "SafeClickButton" which is designed to override the default behaviour of the client-side click (OnClientClick).
Essentially i'm trying to disable the button on click, then do any existing functionality (validation, postback, etc).
It looks to be correctly rendering the HTML (onclick="this.disabled=true;__doPostback...), and it is disabling correctly, but the problem is with the page validation. If any validation on the page has failed, its posting back and THEN showing the validation errors (where it should be done on client side without requiring a postback).
Here's the code for the custom control.
public class SafeClickButton : Button
{
public override string OnClientClick
{
get
{
return string.Format("this.disabled=true;{0}", Page.ClientScript.GetPostBackEventReference(this, string.Empty));
}
set
{
base.OnClientClick = value;
}
}
protected override PostBackOptions GetPostBackOptions()
{
PostBackOptions options = new PostBackOptions(this, string.Empty) {ClientSubmit = true};
if (Page != null)
{
if (CausesValidation && (Page.GetValidators(ValidationGroup).Count > 0))
{
options.PerformValidation = true;
options.ValidationGroup = ValidationGroup;
}
if (!string.IsNullOrEmpty(PostBackUrl))
{
options.ActionUrl = HttpUtility.UrlPathEncode(ResolveClientUrl(PostBackUrl));
}
}
return options;
}
}
What am i doing wrong?
EDIT
Okay so i found part of the problem:
return string.Format("this.disabled=true;{0}", Page.ClientScript.GetPostBackEventReference(this, string.Empty));
Will not apply the dervied behaviour of the postbackoptions.
So i changed it to this:
return string.Format("this.disabled=true;{0}", Page.ClientScript.GetPostBackEventReference(GetPostBackOptions()));
Now the validation is getting fired properly on the client side, but the button isn't re-enabled, FML =)
I think i need to be even smarted now, and say "If validation fails, re-enable button".
Any ideas?
You should be able to just add the Page_ClientValidate method inline. I have never tried this so it might not work:
return string.Format("if (Page_ClientValidate()) { this.disabled=true; {0} } else return false;",
Page.ClientScript.GetPostBackEventReference(this, string.Empty));
You might have to mess with it or add some checks to support GroupValidation but I think this will get you on the right path.
EDIT: I have updated the answer and moved you disable into the if so it only gets disabled when Page_ClientValidate fails.
Check out this link as it is doing what you are looking for I think and illustrates what I was meaning with the Page_ClientValidate:
http://msmvps.com/blogs/anguslogan/archive/2004/12/22/27223.aspx

Resources