Razor Pages Tag Helper with Dynamic Parameters Assigning Current Url as Href - asp.net-core

I'm running into trouble trying to set anchor tag helper parameters dynamically and looking for some help.
I have a nav that is a view component inside the shared _Layout.cshtml page that populates departments from a model.
#model List<DepartmentModel>
<ul class="nav">
#foreach (var d in Model)
{
<li>
<a asp-page="catalog/departments" asp-route-departmentName="#d.Name" asp-route-departmentId="#d.Id">#d.Name</a>
</li>
}
</ul>
Here is the InvokeAsync() from my View Component class
public async Task<IViewComponentResult> InvokeAsync()
{
var departments = _catalogService.GetNavDepartments();
return View(departments);
}
When I first launch the page, all the hrefs are populating correctly.
"catalog/departments/department-name-1/department-id-1"
"catalog/departments/department-name-2/department-id-2"
"catalog/departments/department-name-3/department-id-3"
If I click on one of the links, like the first link for example, I go to the proper department page "catalog/departments/department-name-1/department-id-1"
However, once I click that first link and navigate to the respective page, all the nav hrefs populate to the current url "catalog/departments/department-name-1/department-id-1" instead of the originally generated hrefs. This makes it so I can't navigate to another department.
Here is my route in the Startup.cs
services.AddRazorPages().AddRazorPagesOptions(options => {
options.Conventions.AddPageRoute("/Catalog/Departments", "{dName}/{dId}");
});
Based on the convention above, it eliminates the "catalog/department" piece of the url but I added it in this description for a sense of what I'm trying to accomplish. Even if I add this template to the page that populates the "catalog/departments" url, I get the same result.
#page "{dName}/{dId}"
Can anyone help me figure out what I am missing? Thanks in advance!
******** UPDATE ********
Currently, the only way I am able to get this to work is by adding the cache tag helper.
<cache>
<ul class="nav">
#foreach (var d in Model)
{
<li>
<a asp-page="catalog/departments" asp-route-departmentName="#d.Name" asp-route-departmentId="#d.Id">#d.Name</a>
</li>
}
</ul>
</cache>
This doesn't seem like a proper fix. Seems more of a hack then anything. Anybody have any ideas? Thanks!

Related

How can I get css property of an element which clicked in Blazor?

Here is some A elements in blazor server-side:
<div>
<a href="javascript:void(0)" class="Add" #onclick="SingleAddClick">
<a href="javascript:void(0)" class="Add" #onclick="SingleAddClick">
<a href="javascript:void(0)" class="Add" #onclick="SingleAddClick">
<a href="javascript:void(0)" class="Add" #onclick="SingleAddClick">
<a href="javascript:void(0)" class="Add" #onclick="SingleAddClick">
</div>
All the position of the A elements above is absolute. The Left and Top are differing from each A element.
Now when an A element is clicked, I wanna get the Left and Top of its position.
I need to transfer the js object from .Net method to JS method by JS interop while I don't know how to get the JS object in .Net method.
How can I achieve this?
Thank you.
You can capture a reference to your element as follows:
<a #ref="anchorElement" href="javascript:void(0)" class="Add"
#onclick="SingleAddClick">
#code
{
private ElementReference anchorElement;
}
Now you can call a JSInterop method and pass it the element reference. You should use it in your JS method as though it was retrieved by the getElementById method.
Note: You shouldn't use JavaScript in Blazor. Use #onclick:preventDefault instead of href="javascript:void(0)"
I hope that helps! If you get stuck, let me know
In-order to identify left and top, you'll need to provide a unique identifier (uid) to your every anchor tags. Your uid can either be a ElementReference or a just static (hard-coded) name. With this uid you can identity from where the event is raised from then search it in dom to find relative position to the viewport.
Below are the changes you will need to do to get the elements left and top position.
Razor Component
#inject IJSRuntime JSRuntime // need to inject IJSRuntime to invoke a JavaScript function.
<a id="anchor1" href="" class="Add" #onclick='() => SingleAddClick("anchor1")' #onclick:preventDefault>
#code{
private async Task SingleAddClick(string clickedElementId)
{
//your existing code
// call the javacript method that will be returing something.
var dimensions = await JSRuntime.InvokeAsync<string>("getDimensions", clickedElementId);
// I've used a dynamic type object so that I don't need to create a custom class to desealize it.
dynamic d = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(dimensions);
// Since I've used dynamic keyword thus the type will get resolved at runtime.
// Will throw exception if d is null thus need to be handled properly.
string top = d.top;
string left = d.left;
}
}
JS Library
If you are using any existing js file for interop service then add below javascript method else create a new js file and reference it in _host.
function getDimensions(element) {
return JSON.stringify(document.getElementById(element).getBoundingClientRect());
}
Note: The getBoundingClientRect() method returns the size of an element and its position relative to the viewport.

How to dynamically switch child content in blazor pages

As I'm an absoulute beginner when it comes to web development, I started to look Blazor and learn how to use it to get an easy start in to web developlment and now struggle with a problem.
I have built a Master / Detail page and that page uses a master component (the list of employees) and 2 different detail component (employee readonly detail view and employee edit view).
The master detail page uses the following routes:
https://localhost:44344/masterdetail
https://localhost:44344/masterdetail/{id:int}
https://localhost:44344/masterdetail/{id:int}/edit
I tried to accomplish these goals:
When a user clicks a list entry from the master component, this should be shown in the URL like https://localhost:44344/masterdetail/2 and than load the employee readonly detail view into the detail area
When a user clicks the edit button located on the employee readonly detail view, the master detail page should switch to the employee edit view inside the detail area and show this in the URL like https://localhost:44344/masterdetail/2/edit
When a user clicks the save button located on the employee edit view, the master detail page should switch to the employee readonly detail view inside the detail area and show this in the URL like https://localhost:44344/masterdetail/2
The problems that I have faced:
When the user is in the readonly view and than clicks the edit button, my code is calling NavigationManager.NavigateTo($"/masterdetail/{Id}/edit"); which switches the URL in the address bar of the browser but does not invoke the OnParametersSet() lifecycle method of the master detail page.
Blazor seems to reuse the instance if the [Parameter] Id has not changed it's value.
The same happens when the user is on /masterdetail/{Id}/edit route (entered via browser address bar) and than clicks the save button.
What I learned while researching the problem:
I know that I could use the forceLoad parameter of the
NavigationManager.NavigateTo($"/masterdetail/{Id}/edit", true);
call like this, but this would lead to a complete page refresh
and I'm not sure if this is necessary.
I know that I could use EventCallback<T> in my child components and
react on these events in the parent master detail page but this seems
like a workaround.
I looked for a way to "route inside a blazor page" and stumbled
across topics like "Areas" and "Partial Views" but it looks
like these are MVC concepts.
I also found something called the "RouteView"
(https://github.com/aspnet/AspNetCore/blob/2e4274cb67c049055e321c18cc9e64562da52dcf/src/Components/Components/src/RouteView.cs)
which is a Blazor component but I had no luck using it for my
purposes.
Here is a simplified sample that shows the problem:
Create a new "Blazor App" project in Visual Studio
Choose "Blazor Server App"
Add a new .razor file and paste the code snippet in
Have a look at the comments and the code
Navigate to https://localhost:44344/masterdetail/ and try it yourself
#*Default route for this page when no entry is selected in the master list*#
#page "/masterdetail"
#*Route for this page when an entry is selected in the master list. The detail area should show a readonly view / component*#
#page "/masterdetail/{id:int}"
#*Route for this page when an entry is selected in the master list and the user clicked the edit button in the readonly view / component. The detail area should show a edit view / component*#
#page "/masterdetail/{id:int}/edit"
#using Microsoft.AspNetCore.Components
#inject NavigationManager NavigationManager
<h1>MyMasterDetailPage</h1>
<br />
<br />
<br />
<div>
<h1>Master Area</h1>
<ul class="nav flex-column">
<li class="nav-item px-3">
<button #onclick=#(mouseEventArgs => ShowListItemDetails(1))>Item 1</button>
</li>
<li class="nav-item px-3">
<button #onclick=#(mouseEventArgs => ShowListItemDetails(2))>Item 2</button>
</li>
<li class="nav-item px-3">
<button #onclick=#(mouseEventArgs => ShowListItemDetails(3))>Item 3</button>
</li>
</ul>
</div>
<br />
<br />
<br />
<div>
<h1>Detail Area</h1>
#{
if (_isInEditMode)
{
// In the real project a <EmployeeEditComponent></EmployeeEditComponent> is being used here instead of the h2
<h2>Edit view for item no. #Id</h2>
<h3>Imagine lots of editable fields here e.g. TextBoxes, DatePickers and so on...</h3>
<button #onclick=#SaveChanges> save...</button>
}
else
{
// In the real project a <EmployeeDetailComponent></EmployeeDetailComponent> is being used here instead of the h2
<h2>ReadOnly view for item no. #Id</h2>
<h3>Imagine lots of NON editable fields here. Probably only labels...</h3>
<button #onclick=#SwitchToEditMode> edit...</button>
}
}
</div>
#code {
private bool _isInEditMode;
[Parameter]
public int Id { get; set; }
protected override void OnParametersSet()
{
// This lifecycle method is not called if the [Parameter] has already been set as Blazor seems to reuse the instance if the [Parameter] Id has not changed it's value.
// For example this method is not being called when navigating from /masterdetail/1 to /masterdetail/1/edit
Console.WriteLine($"Navigation parameters have been set for URI: {NavigationManager.Uri}");
_isInEditMode = NavigationManager.Uri.EndsWith("edit");
base.OnParametersSet();
}
private void ShowListItemDetails(int id)
{
Console.WriteLine($"Showing readonly details of item no. {id}");
NavigationManager.NavigateTo($"/masterdetail/{id}");
}
private void SwitchToEditMode()
{
Console.WriteLine("Switching to edit mode...");
NavigationManager.NavigateTo($"/masterdetail/{Id}/edit");
// Setting _isInEditMode = true here would work and update the UI correctly.
// In the real project this method is part of the <EmployeeEditComponent></EmployeeEditComponent> and therefore has no access to _isInEditMode as it belongs to the <MyMasterDetailPage> component.
// I know that I could create a public EventCallback<MouseEventArgs> OnClick { get; set; } in the <EmployeeEditComponent> and react to that event here in the <MyMasterDetailPage> component but is that really the right way to do this?
//_isInEditMode = true;
}
private void SaveChanges()
{
Console.WriteLine("Saving changes made in edit mode and switching back to readonly mode...");
NavigationManager.NavigateTo($"/masterdetail/{Id}");
// Setting _isInEditMode = false here would work and update the UI correctly.
// In the real project this method is part of the <EmployeeDetailComponent></EmployeeDetailComponent> and therefore has no access to _isInEditMode as it belongs to the <MyMasterDetailPage> component
// I know that I could create a public EventCallback<MouseEventArgs> OnClick { get; set; } in the <EmployeeDetailComponent> and react to that event here in the <MyMasterDetailPage> component but is that really the right way to do this?
//_isInEditMode = false;
}
}
My setup:
Visual Studio 2019 16.3.1
.NET Core 3.0 SDK - Windows x64 Installer (v3.0.100)
What is the best practice / recommendation on how to switch child content inside a blazor page?
I asked the question on the AspNetCore Github repo and got an answer.
https://github.com/aspnet/AspNetCore/issues/16653
As "mrpmorris" said, I changed the following lines
Before #page "/masterdetail/{id:int}/edit"
After #page "/masterdetail/{id:int}/{displayMode}"
Before -
After [Parameter]<br> public string DisplayMode { get; set; }
Before _isInEditMode = NavigationManager.Uri.EndsWith("edit");
After string.Equals(DisplayMode, "edit", StringComparison.InvariantCultureIgnoreCase);
and the website behaves as intended and that solves my problem :)

How natively to set active menu item with metalsmith?

i got a simple static site with a main navigation. working with the metalsmith generator.
Is there a native way to set current menu items active?
My current unautomated solution:
I just made a workaround like following.
A MD file page1.md as source of content with some variables i can define at top:
---
title: this is the site title
currentPage1: current
layout: main.html
---
<article class="featurette">
<p class="lead">Some content text...</p>
</article>
and my layout HTML file main.html. Where handlebars is used as engine.
i just post the part of the menu here:
<ul class="nav">
<li>
Link to Page1
</li>
<li>
Link to Page2
</li>
</ul>
both are going through the metalsmith rendering.
I got a current class on the Page1 in the menu.
Question
My solution is working so far, but as my site scales. I need to define the "current" for every site again and again. If I don't watch out this will lead to misconfiguration...
I do like to have freedom on my main navigation markup, as there are some specialities in. So I'm fine with creating this for new pages by myself.
Can i set active menu items somehow with the metalsmith-permalinks or metalsmith-canonical plugin or does there exists a metalsmith plugin suited for this case or maybe with another clever JS manipulation?
Use metalsmith-collections to create a collection of pages
.use(collections({
pages: {
pattern: '*.html'
}
}))
Then in your template loop through them to create your links:
{{#each collections.pages}}
<li>
<a href="{{path}}" {{#if_eq ../id this.id}} class="active" {{/if_eq}}>{{title}}</a>
</li>
{{/each}}
You will need to register a block helper like this: Handlebars.js if block helper ==
Make sure each page ID is unique.
For example:
---
id: phillip
layout: base.hbs
tagline: I haven't thought of one.
pagename: phils page
href: /phil/
navorder: 3
private: true
---

Formatting a list item in jQuery Mobile for a POST link

I have a question about jQuery Mobile formatting, and how to get an <li> to format properly in a listview. The MVC 4 default template for a Mobile application in Visual Studio has a logoff link that uses a GET call to logoff. Here is the markup:
<ul data-role="listview" data-inset="true">
<li>#Html.ActionLink("Change password", "ChangePassword")</li>
<li>#Html.ActionLink("Log off", "LogOff")</li>
</ul>
And here is the call:
// GET: /Account/LogOff
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
With jQuery Mobile, it looks like this:
In contrast, the MVC 4 default template for an internet application uses a POST method for the same logoff:
#using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id =
"logoutForm" })) {#Html.AntiForgeryToken()
<a ref="javascript:document.getElementById('logoutForm').submit()">Log off</a>}
What I would like to do is to use a POST method in my Mobile application to log off instead of the default GET method (in keeping with best practice: Logout: GET or POST?). I can get the link to work properly, but what I am having trouble with is getting the button to look the way it did when it was using the GET method.
My markup looks like this:
<ul data-role="listview" data-inset="true">
<li>#Html.ActionLink("Change password", "ChangePassword")</li>
<li>#using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm" }))
{#Html.AntiForgeryToken()
Log off}
</li>
</ul>
But the result looks like this:
I’ve tried putting the <li> tags around the <a> tag instead, but the results are not any better:
Could someone explain why this <li> tag is being formatted differently than the one above it, and how I might get it to look like the one at the top of this post?
Unless you feel like creating your own custom CSS in that list item don't put the actual <form> inside of it because forms have their own default CSS. Instead keep the form somewhere outside the list item and put the button itself inside the list item
<ul data-role="listview" data-inset="true">
<li>#Html.ActionLink("Change password", "ChangePassword")</li>
<li>Log off</li>
</ul>
#using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm" }))
{
#Html.AntiForgeryToken()
}

How to get Jquery UI tab to be blank

I have a JQuery UI tab dialog that is the detail of a Master-Detail interface. When someone selects an element in the master, the tabs all get their href's populated with URLs giving details of that selected item.
For example, see
http://www.trirand.com/blog/jqgrid/jqgrid.html and browse to Advanced->Master Detail.
But instead of updating a second grid, I'm updating the links of a jquery-ui tabs element like so:
var urls = {
0 : "/url1",
1 : "/url2",
};
jqgrid(....
onSelectRow: function(location_id) {
for (url in urls){
$('#tabs').tabs('url', url , urls[url]+location_id );
}
var selectedTab = $('#tabs').tabs("option", "selected");
$('#tabs').tabs('load', selectedTab);
}
);
$(#tabs.tabs({});
With html like:
<div id="tabs">
<ul>
<li><a id="URL1" href="blank.html">Info</a></li>
<li><a id="URL2" href="blank.html">History</a></li>
</ul>
</div>
I shouldn't have to use a blank.html dummy link. Is there something I can do (when I don't have anything selected in the master) that doesn't cause my tabs to cause a fetch and instead just be empty?
If you set the tab to be blank in your coding nothing will appear in it (obviously), but if you need to empty it on page load use this:
$(document).ready(function() {
$('#divID').empty();
});