Blazor auto scroll Textarea to bottom - blazor-server-side

How can I auto-scroll a TextArea in Blazor, to the bottom, every time its value changes?
To test it, I tried it with an inline JS to change the size of the Element I found here on Stack Overflow:
" oninput="this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px';" "
It works, as long I fill the TextArea manually. But it does not work when filling it from Backend like i want to:
protected async System.Threading.Tasks.Task TestButtonClick0()
{
TextAreaText += ">> SPAM Test \n";
}

There are a couple of ways you could auto-scroll a textarea. However, it probably can't be done with inline JavaScript as events like "onchange" and "oninput" are triggered by user actions. So you'll need to call a JavaScript function to do it when programmatically updating the textarea value. The simplest way would be to add a JavaScript function like:
function scrollToEnd(textarea) {
textarea.scrollTop = textarea.scrollHeight;
}
And then invoke it from the Blazor page:
#page "/"
#using Microsoft.JSInterop
#inject IJSRuntime JS
#functions{
ElementReference TextAreaRef;
string TextAreaText = "Example auto-scroll\n";
void ScrollToEnd() {
JS.InvokeVoidAsync("scrollToEnd", new object[] {TextAreaRef});
}
}
<button class="btn btn-primary m-2" #onclick="ScrollToEnd">Add Line</button>
<br/>
<textarea #ref=TextAreaRef value="#TextAreaText" class="form-control" rows="5"></textarea>
See this Blazor Fiddle for a working example:
https://blazorfiddle.com/s/3ioprd8b

Related

How to change text from in HTML Label from backend

I'm starting to learn ASP.NET Core MVC and just found out that the toolbox is inaccessible/disabled/grayed out, so in html I cannot use <asp:Label/> tag and have to use <label></label> tag instead.
Now I am having trouble changing the string on the HTML Tag <label></label> from the backend.
For this case I already wrote runat="server" inside the tag but still an error occured that says:
The name 'lblkpj' does not exist in the current context
Here is the example html:
<label class="text-center mb-1 fw-bold" runat="server" id="lblkpj"> </label>
and C#:
if (tbl.Rows.Count > 0)
{
lblkpj.text = "Success";
}
else
{
lblkpj.text = "Failed";
}
Where am I going wrong?
Work with Razor markup.
You can implement the logic in View by declaring the variable. And use # symbol. Razor will evaluate the expression and render the output into HTML.
And make sure that you need to return the ViewModel as a List of your model objects from the Controller to View.
View
#model List<YourModel>
#{
string status = Model.Rows.Count > 0 ? "Success" : "Failed";
}
<label class="text-center mb-1 fw-bold" id="lblkpj">#status</label>
Controller
public class YourController : Controller
{
public IActionResult YourView()
{
// Mock data
List<YourModel> list = new List<YourModel>();
return View(list);
}
}

paginated next button not working in asp.net core

I'm new to asp.net MVC core
trying to build a page where you choose multiple search options
I need a get and post actions for that, the results should be in a partial view that is paginated, the code works fine until I click next or previous, I lose the whole search result object cuz the model doesn't bind them back
[HttpGet]
public ViewResult SearchOutbox(DocumentSearchViewModel doc)
{
var documentSearchViewModel = PopulateDocumentSearchViewModel(doc);//this method fills the dropdowns
return View(documentSearchViewModel);
}
[HttpPost]
public async Task<IActionResult> SearchOutbox(DocumentSearchViewModel doc, int? page)
{
var documentSearchViewModel = PopulateDocumentSearchViewModel(doc);
if (ModelState.IsValid)
{
IQueryable<Document> documents = _documentRepository.SearchDocument(documentSearchViewModel);
documentSearchViewModel.Documents = await PaginatedList<Document>.CreateAsync(documents.AsNoTracking(), page ?? 1, 1);
return View("SearchOutbox",documentSearchViewModel);
}
return View();
}
partial view
....
<a asp-action="SearchOutbox"
asp-route-page="#(Model.PageIndex - 1)"
class="btn btn-default #prevDisabled">
Previous
</a>
<a asp-action="SearchOutbox"
asp-route-page="#(Model.PageIndex + 1)"
class="btn btn-default #nextDisabled">
Next
</a>
main view calling partial
<partial name="_ListDocument" model="#Model.Documents">
Before diving into the details, it is worth to mention that for pagination you can use GET only, no need to use POST.
Back yo your question; if the result set is based on some filters, you have to pass all those filters parameters along with the pagination link.
e.g. if you have a URL like below one you can survive by sending only page parameter to the relevant action:
http://example.com/products/?page=1
But whenever you add some filters to the URL, you have to include all of them in the paging buttons, e.g. in below URL you have to send all paramters after ? to the paging action so it can select the next page from the filtered results using the same filtering options:
http://example.com/products/?page=1&category=mobile&brand=xyz
You can add all parameters manually, or you can use a function that will read the query string values then only increase the page number and generate new URL. Below is a function that do the page number increase and replace it via regex:
Previous
Next
#{
string CreateUrl(int newPage)
{
var index = int.Parse(Request.QueryString["page"].ToString());
var input = Request.QueryString.Value;
var replacement = $"page={index + newPage}";
var pattern = #"page=\d+";
return System.Text.RegularExpressions.Regex.Replace(input, pattern, replacement);
}
}
There is some nuget packages that can handle advanced paging functionalities like LazZiya.TagHelpers, install from nuget :
Install-Package LazZiya.TagHelpers -Version 3.0.2
Add paging tag helper to _ViewImports :
#addTagHelper *, LazZiya.TagHelpers
Then use it where you need a paging control:
<paging
total-records="Model.TotalRecords"
page-no="Model.PageNo"
query-string-key-page-no="page"
query-string-value="#(Request.QueryString.Value)">
</paging>
Notice : in the latest preview version (v3.1.0-preview1) no need to add query-string-value so the tag helper will work like below:
<paging
total-records="Model.TotalRecords"
page-no="Model.PageNo"
query-string-key-page-no="page"
</paging>
See tutorial, live demo and docs

How to detect key press without using an input tag in Blazor

I want to be able to capture keyboard input without using an HTML INPUT tag in Blazor. Once the key is pressed i will display a graphic to represent the letter pressed.
Something like this
#page "/counter"
#using Microsoft.AspNetCore.Components.Web
<div #onkeypress="e => KeyPress(e)">
Press any letter key
</div>
#code {
private void KeyPress(KeyboardEventArgs e)
{
var letter = e.Key;
}
}
The KeyPress method does not appear to be called when I set a breakpoint on it. Any help much appreciated.
If there is still someone who want the solution. I think now in .NET 5 you can achieve this in Blazor without js. To set focus and tabindex is important, when you lost focus, or set focus to another element this will not work. This works for me:
<table #ref="testRef" tabindex="0" #onkeydown="HandleKeyDown">
<thead>
<tr>
<th>
Pressed Key
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
#pressedKey
</td>
</tr>
</tbody>
</table>
private ElementReference testRef;
private string pressedKey;
private void HandleKeyDown(KeyboardEventArgs e)
{
pressedKey = e.Key;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await testRef.FocusAsync();
}
}
You are almost there, but you forgot to make div focused. This is the steps:
0.- Make your div focusable adding tabindex tag:
<div
class="jumbotron"
#onkeydown="#KeyDown"
tabindex="0"
#ref="myDiv" >
<h1 class="display-4">
#letter
</h1>
</div>
1.- Create a js code to set focus to your div, on _Host.cshtml for example:
<script>
window.SetFocusToElement = (element) => {
element.focus();
};
</script>
This function takes an Element reference as the parameter.
2.- Call this function after your component is rendered.
protected ElementReference myDiv; // set by the #ref attribute
protected async override Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("SetFocusToElement", myDiv);
}
}
3.- Implement your own KeyDown:
protected void KeyDown(KeyboardEventArgs e)
{
letter = $"Pressed: [{e.Key}]";
}
Notice that this is not a Blazor issue, is just the default html and js behavior. I learned it writing a game, check it out at Blagario lab.
Running:
Demo at Flappy Blazor Bird
Edited Nov 2019:
Code improved by #Quango (many thanks)
Here is an easier way to do it (without JS).
First create a div like this:
<div #ref="keyDownDiv" #onkeydown="KeyPress"></div>
Add a Variable for your reference to your code:
private ElementReference keyDownDiv;
Now you can use this line:
await keyDownDiv.FocusAsync();
The Focus of the Browser will be set on your div and it will trigger the OnKeyDown event.
Tip: if the user presses any other button, the focus will change and it wont work anymore! In this case just use FocusAsync again.

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 :)

WIndows 8 Metro List View Event Listener

I am trying to create a simple HTML Metro App for Windows 8. I want to display a list view, and based on the clicked item display different content on the screen. It sounds trivial, right?
But it doesn't work! Here is my code:
<div id="frameListViewTemplate" data-win-control="WinJS.Binding.Template">
<img data-win-bind="src: picture" class="thumbnail" />
</div>
<div id="basicListView" data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource : DataExample.itemList.dataSource, itemTemplate: select('#frameListViewTemplate'),onselectionchanged : handler}">
</div>
Than in the defult.js
var myListView = document.getElementById("basicListView").winControl;
myListView.addEventListener("selectionchanged", handler);
And the handler:
function handler() {
console.log("Inside the handler : ");
}
handler.supportedForProcessing = true;
So the handler is never called. My questions are: How can I add an event listener and its handler to the listview control.
How can I recognize which element on the list view was clicked.
P.S.
The listview is displayed properly in my app.
Thank you for help,
J
To get the item that is "clicked", you need to use itemInvoked. Selection changed would happen when the user cross slides on the item to select it, rather than taping/clicking to "invoke" it.
http://msdn.microsoft.com/en-us/library/windows/apps/br211827.aspx has some basic details.