Skip to content

Commit

Permalink
Added possibility to add an item when no item is found (Blazored#215)
Browse files Browse the repository at this point in the history
  • Loading branch information
matijs-toonen authored Mar 22, 2021
1 parent 12baa70 commit f7ef8fb
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 4 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ of `Person` but when a `Person` was selected you wanted the control to bind to a
a `ConvertMethod` The convert method will be invoked by the control when a selection is made and will be passed the type selected. The method will need to handle the conversion
and return the new type.

If you want to allow adding an item based on the search when no items have been found, you can achieve this by providing the `AddItemOnEmptyResultMethod` as a parameter.
This method will make the `NotFoundTemplate` selectable the same way a item would normally be, and will be invoked when the user selects the `NotFoundTemplate`.
This method passes the `SearchText` and expects a new item to be returned.

### Local Data Example
```cs
Expand Down
47 changes: 47 additions & 0 deletions samples/BlazorServer/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,44 @@
{
<p>@person.Firstname @person.Lastname</p>
}
<hr />
}

<h1>Blazored Typeahead - Multi-select - Adding items on empty search result</h1>

<BlazoredTypeahead SearchMethod="GetPeopleLocal"
@bind-Values="SelectedPeopleWithNotFoundTemplate"
Disabled="IsDisabledMulti"
EnableDropDown="true"
MinimumLength="2"
AddItemOnEmptyResultMethod="ItemAddedMethod"
placeholder="Search by first name...">
<SelectedTemplate Context="person">
@person.Firstname
</SelectedTemplate>
<HelpTemplate>
Please enter a minimum of 2 characters to perform a search.
</HelpTemplate>
<NotFoundTemplate Context="searchText">
Add "@searchText" as a new user <br />
The newly added user will be selected after being added.
</NotFoundTemplate>
<ResultTemplate Context="person">
@person.Firstname @person.Lastname (Id: @person.Id)
</ResultTemplate>
</BlazoredTypeahead>
<button class="btn btn-primary" style="margin-top: 20px;" @onclick="@(() => IsDisabledMulti = !IsDisabledMulti)">@(IsDisabledMulti ? "Enable" : "Disable")</button>
<hr />

@if (SelectedPeopleWithNotFoundTemplate != null)
{
<p>Selected People:</p>
foreach (var person in SelectedPeopleWithNotFoundTemplate)
{
<p>@person.Firstname @person.Lastname</p>
}
<hr />
}

@code {

Expand All @@ -164,6 +200,7 @@
private Person SelectedPersonNull;
private int? SelectedPersonId;
private IList<Person> SelectedPeople;
private IList<Person> SelectedPeopleWithNotFoundTemplate;
private FormExample FormModel = new FormExample();

protected override void OnInitialized()
Expand Down Expand Up @@ -242,4 +279,14 @@
{
return People.FirstOrDefault(p => p.Id == id);
}

private readonly Random _random = new Random();
private Task<Person> ItemAddedMethod(string searchText)
{
var randomPerson = People[_random.Next(People.Count - 1)];
var newPerson = new Person(_random.Next(1000, int.MaxValue), searchText, randomPerson.Lastname, _random.Next(10, 70), randomPerson.Location);
People.Add(newPerson);
return Task.FromResult(newPerson);
}

}
45 changes: 45 additions & 0 deletions samples/BlazorWebAssembly/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,45 @@
{
<p>@person.Firstname @person.Lastname</p>
}
<hr />
}

<h1>Blazored Typeahead - Multi-select - Adding items on empty search result</h1>

<BlazoredTypeahead SearchMethod="GetPeopleLocal"
@bind-Values="SelectedPeopleWithNotFoundTemplate"
Disabled="IsDisabledMulti"
EnableDropDown="true"
MinimumLength="2"
AddItemOnEmptyResultMethod="ItemAddedMethod"
placeholder="Search by first name...">
<SelectedTemplate Context="person">
@person.Firstname
</SelectedTemplate>
<HelpTemplate>
Please enter a minimum of 2 characters to perform a search.
</HelpTemplate>
<NotFoundTemplate Context="searchText">
Add "@searchText" as a new user <br />
The newly added user will be selected after being added.
</NotFoundTemplate>
<ResultTemplate Context="person">
@person.Firstname @person.Lastname (Id: @person.Id)
</ResultTemplate>
</BlazoredTypeahead>
<button class="btn btn-primary" style="margin-top: 20px;" @onclick="@(() => IsDisabledMulti = !IsDisabledMulti)">@(IsDisabledMulti ? "Enable" : "Disable")</button>
<hr />

@if (SelectedPeopleWithNotFoundTemplate != null)
{
<p>Selected People:</p>
foreach (var person in SelectedPeopleWithNotFoundTemplate)
{
<p>@person.Firstname @person.Lastname</p>
}
<hr />
}

<h1>Blazored Typeahead - Header and footer templates</h1>

<BlazoredTypeahead SearchMethod="GetPeopleLocal"
Expand Down Expand Up @@ -187,6 +222,7 @@
private Person SelectedPersonNull;
private int? SelectedPersonId;
private IList<Person> SelectedPeople;
private IList<Person> SelectedPeopleWithNotFoundTemplate;
private FormExample FormModel = new FormExample();
private BlazoredTypeahead<Person, Person> LocalBlazoredTypeahead;

Expand Down Expand Up @@ -266,4 +302,13 @@
{
return People.FirstOrDefault(p => p.Id == id);
}

private readonly Random _random = new Random();
private Task<Person> ItemAddedMethod(string searchText)
{
var randomPerson = People[_random.Next(People.Count - 1)];
var newPerson = new Person(_random.Next(1000, int.MaxValue), searchText, randomPerson.Lastname, _random.Next(10, 70), randomPerson.Location);
People.Add(newPerson);
return Task.FromResult(newPerson);
}
}
16 changes: 13 additions & 3 deletions src/Blazored.Typeahead/BlazoredTypeahead.razor
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,19 @@

@if (NotFoundTemplate != null)
{
<div class="blazored-typeahead__notfound">
@NotFoundTemplate
</div>
@if (AddItemOnEmptyResultMethod != null)
{
<div class="blazored-typeahead__result @GetSelectedSuggestionClass(0)"
@onclick="@SelectNotFoundPlaceholder">
@NotFoundTemplate(SearchText)
</div>
}
else
{
<div class="blazored-typeahead__notfound">
@NotFoundTemplate(SearchText)
</div>
}
}
else
{
Expand Down
30 changes: 29 additions & 1 deletion src/Blazored.Typeahead/BlazoredTypeahead.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
Expand Down Expand Up @@ -35,8 +36,9 @@ public partial class BlazoredTypeahead<TItem, TValue> : ComponentBase, IDisposab

[Parameter] public Func<string, Task<IEnumerable<TItem>>> SearchMethod { get; set; }
[Parameter] public Func<TItem, TValue> ConvertMethod { get; set; }
[Parameter] public Func<string, Task<TItem>> AddItemOnEmptyResultMethod { get; set; }

[Parameter] public RenderFragment NotFoundTemplate { get; set; }
[Parameter] public RenderFragment<string> NotFoundTemplate { get; set; }
[Parameter] public RenderFragment HelpTemplate { get; set; }
[Parameter] public RenderFragment<TItem> ResultTemplate { get; set; }
[Parameter] public RenderFragment<TValue> SelectedTemplate { get; set; }
Expand Down Expand Up @@ -272,6 +274,10 @@ private async Task HandleKeyup(KeyboardEventArgs args)
{
await SelectTheFirstAndOnlySuggestion();
}
else if (args.Key == "Enter" && ShowNotFound() && AddItemOnEmptyResultMethod != null)
{
await SelectNotFoundPlaceholder();
}
else if (args.Key == "Enter" && SelectedIndex >= 0 && SelectedIndex < Suggestions.Count())
{
await SelectResult(Suggestions[SelectedIndex]);
Expand Down Expand Up @@ -369,6 +375,13 @@ private string GetSelectedSuggestionClass(TItem item, int index)
return Equals(value, Value) ? resultClass : string.Empty;
}

private string GetSelectedSuggestionClass(int index)
{
const string resultClass = "blazored-typeahead__active-item";

return index == SelectedIndex ? resultClass : string.Empty;
}

private async void Search(Object source, ElapsedEventArgs e)
{
if (_searchText.Length < MinimumLength)
Expand Down Expand Up @@ -422,6 +435,21 @@ private async Task SelectResult(TItem item)
Initialize();
}

private async Task SelectNotFoundPlaceholder()
{
Debug.Assert(AddItemOnEmptyResultMethod != null);
try
{
// Potentially dangerous code
var item = await AddItemOnEmptyResultMethod(SearchText);
await SelectResult(item);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}

private bool ShouldShowHelpTemplate()
{
return SearchText.Length > 0 &&
Expand Down

0 comments on commit f7ef8fb

Please sign in to comment.