Routing to named element in Blazor (use anchor to navigate to specific element) - html

I cannot use an HTML anchor to navigate to a specific HTML element of a page in the Blazor Server. For example:
#page "/test"
<nav>
<!-- One version I've tried -->
Section2
<!-- Another version I've tried -->
<NavLink href="#section2">Section2</NavLink>
</nav>
#* ... *#
<h2 id="section2">It's Section2.</h2>
#* ... *#
When I click the link to Section2, I get redirected to the route http://localhost:5000/test#section2, however, will be at the top of the page. In my opinion, the browser should scroll down to the proper element, as specified by the Element Selector, but it can't.
Does it have to be done in a special way in Blazor?
I use Blazor 6 in .Net6 with Visual Studio 2022 (ver:17.0.2).

After loading a page, a browser automatically scrolls to the element identified by its id in the fragment part of the URL. It does the same when you click on an anchor with an href of the kind #element-id.
The page load behavior doesn't work for a Blazor Server because the element doesn't exist yet on page load.
The solution is to manually create a scroller using javascript and a razor component:
First of all, create a razor component like this
#inject IJSRuntime JSRuntime
#inject NavigationManager NavigationManager
#implements IDisposable
#code {
protected override void OnInitialized()
{
NavigationManager.LocationChanged += OnLocationChanged;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await ScrollToFragment();
}
public void Dispose()
{
NavigationManager.LocationChanged -= OnLocationChanged;
}
private async void OnLocationChanged(object sender, LocationChangedEventArgs e)
{
await ScrollToFragment();
}
private async Task ScrollToFragment()
{
var uri = new Uri(NavigationManager.Uri, UriKind.Absolute);
var fragment = uri.Fragment;
if (fragment.StartsWith('#'))
{
// Handle text fragment (https://example.org/#test:~:text=foo)
// https://github.com/WICG/scroll-to-text-fragment/
var elementId = fragment.Substring(1);
var index = elementId.IndexOf(":~:", StringComparison.Ordinal);
if (index > 0)
{
elementId = elementId.Substring(0, index);
}
if (!string.IsNullOrEmpty(elementId))
{
await JSRuntime.InvokeVoidAsync("BlazorScrollToId", elementId);
}
}
}
}
Then add this javascript code somewhere before the Blazor script renders. You can wrap it with script tags and place it in the head.
function BlazorScrollToId(id) {
const element = document.getElementById(id);
if (element instanceof HTMLElement) {
element.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest"
});
}
}
Finally implement it in your pages if needed. You can also place it inside your layouts, so it will work for every page you create.
#page "/"
<PageTitle>Index</PageTitle>
<a href="#my-id">
<h1>Hello, world!</h1>
</a>
<SurveyPrompt Title="How is Blazor working for you?" />
<div style="height: 2000px">
</div>
<div id="my-id">
Hello!
</div>
<AnchorNavigation />
Source: link

You can also use an ElementReference and FocusAsync which uses the built in Blazor JS. To use it you need to use a small hack to make the component "Focusable" which is to set a tabindex. I've used a span but you can use what you like. I've used #alessandromanzini's code to get the element from the NavigationManager.
Here's a component:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.Routing;
using System.Diagnostics.CodeAnalysis;
namespace SO75358165;
public class Bookmark : ComponentBase, IDisposable
{
private bool _setFocus;
[Inject] private NavigationManager NavManager { get; set; } = default!;
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public string? BookmarkName { get; set; }
[DisallowNull] public ElementReference? Element { get; private set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "span");
builder.AddAttribute(2, "tabindex", "-1");
builder.AddContent(3, this.ChildContent);
builder.AddElementReferenceCapture(4, this.SetReference);
builder.CloseElement();
}
protected override void OnInitialized()
=> NavManager.LocationChanged += this.OnLocationChanged;
protected override void OnParametersSet()
=> _setFocus = this.IsMe();
private void SetReference(ElementReference reference)
=> this.Element = reference;
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
if (this.IsMe())
{
_setFocus = true;
this.StateHasChanged();
}
}
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (_setFocus)
await this.Element!.Value.FocusAsync(false);
_setFocus = false;
}
private bool IsMe()
{
string? elementId = null;
var uri = new Uri(this.NavManager.Uri, UriKind.Absolute);
if (uri.Fragment.StartsWith('#'))
{
elementId = uri.Fragment.Substring(1);
return elementId == BookmarkName;
}
return false;
}
public void Dispose()
=> NavManager.LocationChanged -= this.OnLocationChanged;
}
Here's my test page:
#page "/"
<PageTitle>Index</PageTitle>
<NavLink href="#me">To me</NavLink>
<h1>Hello, world!</h1>
<h1>Hello, world!</h1>
<h1>Hello, world!</h1>
//.....
<h1>Hello, world!</h1>
<Bookmark BookmarkName="me" >
<h1 id="me">Focus on Me</h1>
</Bookmark>

Related

How do I restart a component in a Blazor page

I have a Syncfusion SfDialog in my code and I need the component in the content to restart every time the dialog is open. So far I have tried this:
<SfDialog Visible="_dialogTripRunAutoRoute" Width="75%" ShowCloseIcon="true" IsModal="true" AllowPrerender="true">
<DialogEvents Closed="#CloseDialogTripRunAutoRoute"></DialogEvents>
<DialogTemplates>
<Content>
#_tripRunAutoRoute
</Content>
</DialogTemplates>
<DialogPositionData X="center" Y="top"></DialogPositionData>
</SfDialog>
private async Task ToggleDialogTripRunAutoRoute(){
_tripRunAutoRoute = new TripRunAutoRoute();
_tripRunAutoRoute.ModelTripRun = TripOps.TripRunAutoRouteFormModel;
await InvokeAsync(StateHasChanged);
_dialogTripRunAutoRoute = !_dialogTripRunAutoRoute;
}
The result is
Assumption. #_tripRunAutoRoute is just a plain old RenderFragment.
You can't "restart" a component. The Renderer controls the component's lifecycle, not you. Any attempt to reset its state internally will be reverted the next time the page renders.
Move the toggle parameter outside the control like this:
#if(_dialogTripRunAutoRoute)
{
SfDialog stuff
}
Now the Renderer will remove the component from the render tree when _dialogTripRunAutoRoute is false, and create a new instance when it's true.
You can use the Opened and Closed event of the Dialog control to re render your component added in the Dialog content. Refer the API and code below,
<div class=" col-lg-8 control-section sb-property-border" id="target" style="height:350px;">
<div>
#if (this.ShowButton)
{
<button class="e-btn" #onclick="#OnBtnClick">Open</button>
}
</div>
<SfDialog Width="335px" IsModal="true" #bind-Visible="Visibility" AllowPrerender="true" CssClass="dialog-medium">
<DialogTemplates>
<Header> Software Update </Header>
<Content>
#if(DialogBool)
{
#DialogContent
<div>#count</div>
}
</Content>
</DialogTemplates>
<DialogButtons>
<DialogButton Content="OK" IsPrimary="true" OnClick="#DlgButtonClick" />
</DialogButtons>
<DialogEvents OnOpen="#DialogOpen" Closed="#DialogClose"></DialogEvents>
<DialogAnimationSettings Effect="#DialogEffect.None"></DialogAnimationSettings>
</SfDialog>
#code {
SfCheckBox<bool> CheckboxObj;
public int count { get; set; } = 0;
public bool DialogBool { get; set; } = false;
public string DialogContent { get; set; } = "";
private bool Visibility { get; set; } = true;
private bool ShowButton { get; set; } = false;
private void DialogOpen(Object args)
{
this.ShowButton = false;
DialogBool = true;
}
private void DialogClose(Object args)
{
this.ShowButton = true;
DialogBool = false;
}
private void OnBtnClick()
{
this.Visibility = true;
DialogContent = "content added";
count++;
}
private void DlgButtonClick()
{
this.Visibility = false;
}
}
API Link: https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Popups.DialogEvents.html#Syncfusion_Blazor_Popups_DialogEvents_Opened
I need the component in the content to restart every time the dialog is open
You can make Blazor restart the component by assigning the key attribute to the component. When you change the key, Blazor re-creates the component in the DOM, thus, you can call it as restarted or rebooted.
<ComponentInTheContent #key="#(componentId)">
</ComponentInTheContent>
#code {
private Guid componentId = Guid.NewGuid();
private async Task CalledWhenDialogIsOpened()
{
// stuff
// this change of id will make Blazor re-create
// the component in the DOM as it sees it as a new component.
componentId = Guid.NewGuid();
}
}

How to reproduce #using(Html.BeginForm()){ ... } in Razor [duplicate]

In WebForms, I could create a component where I could embed my own content
Example
<uc:BootstrapModal Title="Hello World" Size="Large">
<h1>Hello World</h1>
</uc:BootstrapModal>
<!--generates this...-->
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<h1>Hello World</h1>
</div>
</div>
</div>
How can I do this in MVC?
You can create a HtmlHelper extension method to generate the enclosing html, similar to the way BeginForm() generates enclosing <form></form> tags.
using System;
using System.Web.Mvc;
namespace YourAssembly.Html
{
public class Dialog : IDisposable
{
private readonly ViewContext _viewContext;
private bool _disposed;
public Dialog(ViewContext viewContext)
{
if (viewContext == null)
{
throw new ArgumentNullException("viewContext");
}
_viewContext = viewContext;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
DialogExtensions.EndDialog(_viewContext);
}
}
public void EndDialog()
{
Dispose(true);
}
}
public static class DialogExtensions
{
public static Dialog BeginDialog(this HtmlHelper htmlHelper)
{
return DialogHelper(htmlHelper);
}
private static Dialog DialogHelper(this HtmlHelper htmlHelper)
{
TagBuilder div = new TagBuilder("div");
div.AddCssClass("modal fade bs-example-modal-lg");
div.MergeAttribute("tabindex", "-1");
div.MergeAttribute("role", "dialog");
htmlHelper.ViewContext.Writer.Write(div.ToString(TagRenderMode.StartTag));
div = new TagBuilder("div");
div.AddCssClass("modal-dialog modal-lg");
htmlHelper.ViewContext.Writer.Write(div.ToString(TagRenderMode.StartTag));
div = new TagBuilder("div");
div.AddCssClass("modal-content");
htmlHelper.ViewContext.Writer.Write(div.ToString(TagRenderMode.StartTag));
Dialog modal = new Dialog(htmlHelper.ViewContext);
return modal;
}
public static void EndDialog(this HtmlHelper htmlHelper)
{
EndDialog(htmlHelper.ViewContext);
}
internal static void EndDialog(ViewContext viewContext)
{
viewContext.Writer.Write("</div>");
viewContext.Writer.Write("</div>");
viewContext.Writer.Write("</div>");
}
}
}
and in the view use it as
#using (Html.BeginDialog())
{
// add the content to be rendered in the dialog here
}
Note: In the web.config file add the namespace of your assembly so that you do not have to include #using statements in the view.
<namespaces>
<add namespace="System.Web.Mvc" />
....
<add namespace="YourAssembly.Html" /> <!--add-->
</namespaces>
And you can then extend this by creating additional overloads, for example you might also have parameters for string title and a ButtonType buttons (an enum) to render a title bar and footer buttons in the dialog

Jxbrowser with tinymce and spellcheck

I use jxbrowser in swing application as embeded browser. Jxbrowser has option to spellCheck which works fine.
Now I must use rich text editor like tinyMce and spellCheck doesn't work with it.
How can I do that spellCheck will work with tinyMCe in jxbrowser?
java class:
public class SpellCheckerSample {
public static void main(String[] args) throws Exception {
// Enable heavyweight popup menu for heavyweight (default) BrowserView component.
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
Browser browser = new Browser();
BrowserView view = new BrowserView(browser);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.add(view, BorderLayout.CENTER);
frame.setSize(700, 500);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
BrowserContext context = browser.getContext();
SpellCheckerService spellCheckerService = context.getSpellCheckerService();
spellCheckerService.addSpellCheckListener(new SpellCheckListener() {
#Override
public void onSpellCheckCompleted(SpellCheckCompletedParams params) {
String text = params.getText();
System.out.println(params.getResults().size());
System.out.println("text = " + text);
List<SpellCheckResult> mistakes = params.getResults();
for (SpellCheckResult mistake : mistakes) {
System.out.println("mistake.getStartIndex() = " + mistake.getStartIndex());
System.out.println("mistake.getLength() = " + mistake.getLength());
}
}
});
// Enable SpellChecker service.
spellCheckerService.setEnabled(true);
// Configure SpellChecker's language.
spellCheckerService.setLanguage("en-US");
browser.setContextMenuHandler(new MyContextMenuHandler(view, browser));
//browser.loadHTML(loadHtml);
browser.loadURL("C:\\tiny.html");
}
private static class MyContextMenuHandler implements ContextMenuHandler {
private final JComponent component;
private final Browser browser;
private MyContextMenuHandler(JComponent parentComponent, Browser browser) {
this.component = parentComponent;
this.browser = browser;
}
public void showContextMenu(final ContextMenuParams params) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JPopupMenu popupMenu = createPopupMenu(params);
Point location = params.getLocation();
popupMenu.show(component, location.x, location.y);
}
});
}
private JPopupMenu createPopupMenu(final ContextMenuParams params) {
final JPopupMenu result = new JPopupMenu();
// Add suggestions menu items.
List<String> suggestions = params.getDictionarySuggestions();
for (final String suggestion : suggestions) {
result.add(createMenuItem(suggestion, new Runnable() {
public void run() {
browser.replaceMisspelledWord(suggestion);
}
}));
}
if (!suggestions.isEmpty()) {
// Add the "Add to Dictionary" menu item.
result.addSeparator();
result.add(createMenuItem("Add to Dictionary", new Runnable() {
public void run() {
String misspelledWord = params.getMisspelledWord();
browser.addWordToSpellCheckerDictionary(misspelledWord);
}
}));
}
return result;
}
private static JMenuItem createMenuItem(String title, final Runnable action) {
JMenuItem result = new JMenuItem(title);
result.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
action.run();
}
});
return result;
}
}
}
tiny.html:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="tinymce/tinymce.min.js"></script>
<script>tinymce.init({ selector:'textarea' });</script>
</head>
<body>
<textarea>Test eror</textarea>
</body>
</html>
Solution is to init tinyMCe with browser_spellcheck:true
<script>tinymce.init({
selector:'textarea' ,
browser_spellcheck: true,
contextmenu: false
});</script>

Vaadin Drag Drop Component

We are creating a web application using Vaadin. Our application contains alot of drag and drop features.
We have an object which is drag-able.
We can click on it to open its menu as well.
Sometimes that when we click that item it behaves as if it is dragged.
When this happens we are unable to open its menu because the component is in dragmode.
All components with the same functionality behave the same however in development environment, when we restart the tomcat the problem disappeared?
I noticed that when the components start showing me this behavior the webpage in FireFox the behavior is fine there?
A simple solution to this could be to introduce a drag mode/edit button which would allow the user to switch the drag mode on and off.
This would mean the user could interact with the components and then enter this "drag mode" when they wished to drag them. Hence reducing the frustration of trying to interact with the component and it starting to "drag" instead.
I've create a simple example program to try out below.
public class DemoUI extends UI {
HorizontalSplitPanel splitPanel;
DragAndDropWrapper wrapperA;
DragAndDropWrapper wrapperB;
DragAndDropWrapper splitPaneWrapper;
Button buttonA;
Button buttonB;
private boolean isDragMode = false;
#WebServlet(value = "/*", asyncSupported = true)
#VaadinServletConfiguration(productionMode = false, ui = DemoUI.class)
public static class Servlet extends VaadinServlet {
}
#Override
protected void init(VaadinRequest request) {
final HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
Button buttonA = new Button("Button A");
Button buttonB = new Button("Button B");
final DragAndDropWrapper wrapperA = new DragAndDropWrapper(buttonA);
final DragAndDropWrapper wrapperB = new DragAndDropWrapper(buttonB);
final VerticalLayout leftPanel = new VerticalLayout();
final VerticalLayout rightPanel = new VerticalLayout();
DragAndDropWrapper leftPanelWrapper = new DragAndDropWrapper(leftPanel);
DragAndDropWrapper rightPanelWrapper = new DragAndDropWrapper(rightPanel);
buttonA.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
Notification.show("Button A was clicked");
}
});
buttonB.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
Notification.show("Button B was clicked");
}
});
leftPanelWrapper.setDropHandler(new DropHandler() {
#Override
public void drop(DragAndDropEvent event) {
leftPanel.addComponent(event.getTransferable().getSourceComponent());
}
#Override
public AcceptCriterion getAcceptCriterion() {
return AcceptAll.get();
}
});
rightPanelWrapper.setDropHandler(new DropHandler() {
#Override
public void drop(DragAndDropEvent event) {
rightPanel.addComponent(event.getTransferable().getSourceComponent());
}
#Override
public AcceptCriterion getAcceptCriterion() {
return AcceptAll.get();
}
});
final Button dragMode = new Button("Drag Mode On");
dragMode.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
isDragMode = !isDragMode;
if (isDragMode) {
dragMode.setCaption("Drag Mode Off");
wrapperA.setDragStartMode(DragStartMode.WRAPPER);
wrapperB.setDragStartMode(DragStartMode.WRAPPER);
} else {
dragMode.setCaption("Drag Mode On");
wrapperA.setDragStartMode(DragStartMode.NONE);
wrapperB.setDragStartMode(DragStartMode.NONE);
}
}
});
leftPanel.setSizeFull();
rightPanel.setSizeFull();
leftPanelWrapper.setSizeFull();
rightPanelWrapper.setSizeFull();
leftPanel.addComponent(wrapperA);
rightPanel.addComponent(wrapperB);
splitPanel.setFirstComponent(leftPanelWrapper);
splitPanel.setSecondComponent(rightPanelWrapper);
splitPanel.setSizeFull();
VerticalLayout layout = new VerticalLayout();
layout.addComponent(dragMode);
layout.addComponent(splitPanel);
layout.setSizeFull();
this.setContent(layout);
this.setSizeFull();
}
.
All the best.

load html at listview item onclick

I'm now stuck in html loading from my assets folder. I've several html pages under assets folder and have to load those at listview item onclick. Each listview item own their html pages.Does anybody know how i can get onclick event and how to show specific html pages ?
Thanks
public class WebViewWithListActivity extends Activity {
private String lv_arr[] = { "Android", "iPhone", "BlackBerry"};
ListView lv1;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
lv1 = (ListView) findViewById(R.id.listView1);
lv1.setAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, lv_arr));
lv1.setTextFilterEnabled(true);
lv1.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> arg0, View arg1, int position,long id)
{
AlertDialog.Builder adb = new AlertDialog.Builder(WebViewWithListActivity.this);
adb.setTitle("Selected item");
adb.setMessage("Selected Item is = "
+ lv1.getItemAtPosition(position));
adb.setPositiveButton("Ok", null);
Log.i("Selected item is ",(String) lv1.getItemAtPosition(position)+"" );
adb.show();
//TextView tvUrl = (TextView) view.findViewById(R.id.item2);
TextView tvUrl=(TextView) findViewById(R.id.item2);
if(lv1.getItemAtPosition(position).equals("Android"))
{
GlobalVariable.SetURL("http://www.google.co.in/");
Log.i("Global vari : ",GlobalVariable.GetURL());
Intent i = new Intent(WebViewWithListActivity.this,WebViewDemo.class);
//i.putExtra("http://www.google.co.in/", tvUrl.getText());
startActivity(i);
}
}
});
}
}
This is WebViewDemo. I simply extended Activity:
public class WebViewDemo extends Activity{
private WebView webView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_webview);
//String url =getIntent().getStringExtra("url");//get url that pass from the other screen
//Log.i("url ", url+"");
Log.i("Global vari : ",GlobalVariable.GetURL());
webView = (WebView)findViewById(R.id.wvDisplay);
WebSettings webSetting= webView.getSettings(); //create new settings for webView
webSetting.setJavaScriptEnabled(true); // enabled javascript
webView.setWebViewClient(new WebViewClient()); //set up webviewclient, this set not to open the default browser when link click
//Log.i("url ", url+"");
webView.loadUrl(GlobalVariable.GetURL());//load the web page
}
}
public class GlobalVariable extends Application{
private static String url;
public static String GetURL()
{
return url;
}
public static void SetURL(String URL) {
url = URL;
}
}
You can use ListView's setOnItemClickListener method to get click event on list items.
You can use WebView's loadUrl method to show your html pages. See WebView tutorial.
Dude ... give up ... it won't work -- no way to get this listener working when you have a webview inside a listview
You can use a custom webViewClient and catch links in the html, then you can do what ever you want will the clicks.