Hi @thaithiendi
Can you try using data-bs-dismiss="modal"
since ABP uses Bootstrap 5.
https://getbootstrap.com/docs/5.0/components/modal/#modal-components
Hi @TonyH
You can publish an event inside the application to call StateHasChanged() method.
I'll share an example for UnreadCount for notifications.
Firstly I'll create a NotificationData class and register it as scoped into service collection. Scoped
is important here because if you define it as Transient, a new instance will be created each time and it can't keep its state. If you define it as Singleton, it's ok in Blazor WASM but it won't work on Blazor Server, all the connected users will use the same instance of that object. So, Scoped is the best way in here.
1- Define NotificationData.cs
first.
public class NotificationData : IScopedDependency
{
private int unreadCount;
public int UnreadCount
{
get => unreadCount;
set
{
unreadCount = value;
UnreadCountChanged?.Invoke(this, new EventArgs());
}
}
public event EventHandler UnreadCountChanged;
}
2- Create a notification component with name NotificationsComponent.razor
for toolbar
@inject NotificationData NotificationData
<div class="nav-link">
<i class="fa fa-bell"></i>
@if (NotificationData.UnreadCount > 0)
{
<span class="position-absolute top-0 badge rounded-pill bg-danger">
@NotificationData.UnreadCount
<span class="visually-hidden">unread messages</span>
</span>
}
</div>
@code{
protected override Task OnInitializedAsync()
{
NotificationData.UnreadCountChanged += (s, e) => StateHasChanged();
return base.OnInitializedAsync();
}
}
3- Add it to the toolbar
public class MyToolbarContributor : IToolbarContributor
{
public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
{
if(context.Toolbar.Name == StandardToolbars.Main)
{
context.Toolbar.Items.Add(new ToolbarItem(typeof(NotificationsComponent)));
}
return Task.CompletedTask;
}
}
4- Don't forget to configure it in the module file
Configure<AbpToolbarOptions>(options =>
{
options.Contributors.Add(new MyToolbarContributor());
});
5- Inject the NotificationData
wherever you want and make changes. (In my case I've injected it into Index.razor)
@inject NotificationData NotificationData
<Button Color="Color.Success" @onclick="@(()=> NotificationData.UnreadCount++)" >Increase Unread Count</Button>
<Button Color="Color.Danger" @onclick="@(()=> NotificationData.UnreadCount--)" >Decrease Unread Count</Button>
6- See the result:
Hi, I have build the project success, but I got a error is like this when I visit the file management page:
[15:57:54 ERR] Failed executing DbCommand (2ms) [Parameters=[@__ef_filter__p_0='?' (DbType = Boolean)], CommandType='Text', CommandTimeout='30'] SELECT COUNT(*) FROM [FmDirectoryDescriptors] AS [f] WHERE ((@__ef_filter__p_0 = CAST(1 AS bit)) OR ([f].[TenantId] IS NULL)) AND ([f].[ParentId] IS NULL) [15:57:54 ERR] An exception occurred while iterating over the results of a query for context type 'Volo.FileManagement.EntityFrameworkCore.FileManagementDbContext'. Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid object name 'FmDirectoryDescriptors'. at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__188_0(Task`1 result) at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke() at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location --- at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) --- End of stack trace from previous location --- at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync() ClientConnectionId:b5ccd131-29d0-416a-a46e-8262476988b0 Error Number:208,State:1,Class:16
It seem looks like the database don't have specified FmDirectoryDescriptors table/entity, but when I use
dotnet ef migrations xxx
, and I got nothing migration script, what I should to do to generate FmDirectoryDescriptors table/entity? PS: This entity's name looks like strange.
It seems your database wasn't updated. Did you install FileManagement module via ABP CLI/Suite or just installed manually?
Make sure you've added builder.ConfigureFileManagement()
method in your DbContext before adding migration
You can consider running that job on a Linux server or configure a wsl2 terminal for that operation.
As a best practice, I suggest you separate your project into modules. In this way, you can generate your client-proxies module by module, This way reduces JSON sizes. Then you won't get any 'Maximum call stack size exceeded' exception.
Hello @Kaspars.Avotin
We've understood the problem. It happens only in win32-x64
systems.
https://github.com/sass/node-sass/issues/1863#issuecomment-299136177
We tried on Ubuntu 20.04 with your json file and everything worked well. So, the problem isn't related with ABP Framework. I have a couple of suggestions for you. You can use different operation system to overcome the problem like Linux or MacOS. But it may not be sustainable. So you can use a Linux distribution like Ubuntu on your Windows machine. The best way to do that is using WSL2.
Let me explain step by step about how we configured it.
Run your HttpApi.Host
project on your windows normally.
Expose your localhost to the world with ngrok You can also use IPv4 address to connect to your running application from WSL but there might be a lot of firewall issues according to your computer. This is the best stable way
https://localhost:44360
, use the following command to expose your application with https scheme
.\ngrok.exe http https://localhost:44360
Now you're ready to generate your script in WSL.
Make sure you have an Ubuntu with WSL2 on your windows.
Open a new terminal (PS), navigate to your angular project path and switch to the Ubuntu. It'll automatically go to your mounted angular folder in ubuntu.
Make sure nodejs and npm are installed on ubuntu instance. Follow this one: https://github.com/nodesource/distributions/blob/master/README.md#installation-instructions
After installation if npm or ubuntu commands aren't available you can run bash
command once more.
Generate the proxy in ubuntu terminal.
npm run generate-proxy
(It takes much more time, I've just clipped the waiting time from the gif)
Or you can use macOS or Linux development environment. But Windows WSL2 is doing its job well.
Hi @huseyininalan
Currently, CmsKit doesn't have Features.
I've created an issue (#13579) for that implementation to the next release.
But I'll share some ways to achieve that on your version.
Let's say we'll add a feature for Pages feature.
Define a FeatureDefinitionProvider
public class CmsKitFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var cmsKit = context.AddGroup(CmsKitFeatures.GroupName);
cmsKit.AddFeature(CmsKitFeatures.Pages, "true", L("Pages"), L("Toggle pages feature"), new ToggleStringValueType());
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<MyProjectNameResource>(name);
}
}
public static class CmsKitFeatures
{
public const string GroupName = "CmsKit";
public const string Pages = GroupName + ".Pages";
}
Override both Admin & Public PagesAppService and add [RequiresFeature]
attribute on them
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.CmsKit.Admin.Pages;
using Volo.CmsKit.Contents;
using Volo.CmsKit.Domain.Shared.Volo.CmsKit;
using Volo.CmsKit.Pages;
using Volo.CmsKit.Public.Pages;
namespace MyCompanyName.MyProjectName;
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(PageAdminAppService))]
[RequiresFeature(CmsKitFeatures.Pages)]
public class MyCmsKitPageAdminAppService : PageAdminAppService
{
public MyCmsKitPageAdminAppService(IPageRepository pageRepository, PageManager pageManager) : base(pageRepository, pageManager)
{
}
}
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(PagePublicAppService))]
[RequiresFeature(CmsKitFeatures.Pages)]
public class MyCmsKitPagePublicAppService : PagePublicAppService
{
public MyCmsKitPagePublicAppService(IPageRepository pageRepository, ContentParser contentParser) : base(pageRepository, contentParser)
{
}
}
Hide it from Admin menu if disabled a. Add a custom menu contributor
public class MyMenuContributor : IMenuContributor
{
public async Task ConfigureMenuAsync(MenuConfigurationContext context)
{
var cmsMenu = context.Menu.FindMenuItem(CmsKitAdminMenus.GroupName);
if (cmsMenu is not null)
{
var featureChecker = context.ServiceProvider.GetRequiredService<IFeatureChecker>();
if (!await featureChecker.IsEnabledAsync(CmsKitFeatures.Pages))
{
cmsMenu.TryRemoveMenuItem(CmsKitAdminMenus.Pages.PagesMenu);
}
}
return;
}
}
b. Configure it to the module file
```csharp
Configure<AbpNavigationOptions>(options =>
{
options.MenuContributors.Add(new MyMenuContributor());
});
```
That's it. You'll see it in the features modal and you'll be able to manage it to the your tenants.
You can apply those methods for each of the CmsKit features.
Hi @AndrewT
We've published ABP 6.0.0-rc.1, so you can start to use PageLayout to set current menu item with that version.
@enisn can you update the label on the menu to show for instance the number of new items and outstanding items dynamically without changing the pages, eg;
Menu text would be "System Issues (2 New; 3 Open)" - where the items in brackets are changed dynamically? and can you change their colour eg "2" would be red, and "3" would be green text.
As an answer to that question;
You can override the Menu item component in blazor project and customize it as you wish. To achieve that, you may follow these steps:
Create a new file in blazor projects named MyMainSiderbarMenuItem.razor
@using Volo.Abp.DependencyInjection
@using Volo.Abp.AspNetCore.Components.Web.LeptonTheme.Components.ApplicationLayout.Navigation
@inherits MainSiderbarMenuItem
@attribute [ExposeServices(typeof(MainSiderbarMenuItem))]
@attribute [Dependency(ReplaceServices = true)]
@if (MenuItem.MenuItem.IsLeaf)
{
var url = MenuItem.MenuItem.Url == null? "#" : MenuItem.MenuItem.Url.TrimStart('/', '~');
<li class="@(MenuItem.IsActive ? "current" : "") @MenuItem.MenuItem.CssClass" id="@MenuItem.MenuItem.ElementId">
<a href="@url" target="@MenuItem.MenuItem.Target" @onclick="() => OnMenuItemClick(MenuItem)">
<span class="lp-icon">
<i class="@(MenuItem.MenuItem.Icon ?? "")"></i>
</span>
<span class="lp-text">
@MenuItem.MenuItem.DisplayName
@*** CUSTOMIZE THIS SECTION 👇 ****@
@if(MenuItem.MenuItem.Name == "MyProjectName.HostDashboard")
{
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
99+
<span class="visually-hidden">unread messages</span>
</span>
}
</span>
</a>
</li>
}
else
{
<li class="@(MenuItem.IsActive ? "current" : "") has-drop">
<a href="#" @onclick:preventDefault @onclick="ToggleMenu">
<span class="lp-icon">
<i class="@(MenuItem.MenuItem.Icon ?? "")"></i>
</span>
<span class="lp-text">
@MenuItem.MenuItem.DisplayName
</span>
<span class="lp-arrow-icon" for="@MenuItem.MenuItem.ElementId">
<i class="fa fa-chevron-down"></i>
</span>
</a>
<ul class="@MenuItem.MenuItem.CssClass" id="@MenuItem.MenuItem.ElementId" style="display:@(MenuItem.IsOpen ? "block" : "none")">
@foreach (var childMenuItem in MenuItem.Items)
{
<MainSiderbarMenuItem Menu="@Menu" MenuItem="@childMenuItem"/>
}
</ul>
</li>
}
Unfortunately there is no way to handle that kinds of multiple-page routes while activating selected menu in the current version. (5.3)
So your credit is refunded.
But, we've developed a feature to solve that kind of issues in v6.0. (https://github.com/abpframework/abp/pull/12840)
You can track the releases from here , 6.0-rc.1 will be released on July 19.
After upgrading to v6.0, you can set the menu name like that:
@inject PageLayout PageLayout
@code {
protected override async Task OnInitializedAsync()
{
PageLayout.MenuItemName = "MyProjectName.Products";
}
}
And it'll automatically update the selected menu without refreshing the page.
Hi @adrianl
I have a couple of questions to determine the issue.
What is your page route?
Also, is that menu highlighted when you refresh the page?
Hi @Sturla
We're aware of the problem and almost solved it. It'll be released in a patch version.
Thanks for your patience.
Also your credit is refunded.