@ -0,0 +1,125 @@ |
|||||
|
# Adding Dark Mode Support to the Basic Theme |
||||
|
Basic Theme uses plain bootstrap and does not have any custom colors & styles. This article will show you how to add dark mode support to the [Basic Theme](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Basic-Theme). |
||||
|
|
||||
|
Bootstrap brings the [Color Modes](https://getbootstrap.com/docs/5.3/customize/color-modes/#dark-mode) feature with version **5.3**. This feature allows you to add dark mode support to your website with a single line of code. Adding the `data-bs-theme="dark"` attribute changes the color mode of the element to dark mode. |
||||
|
|
||||
|
## Instructions |
||||
|
|
||||
|
1. Create a new project with the following command: |
||||
|
```bash |
||||
|
abp new BasicThemeDarkMode -t app --theme basic |
||||
|
``` |
||||
|
|
||||
|
2. Create a component that toggles the color mode. |
||||
|
|
||||
|
- Create a new file named `Components/ChangeTheme/Default.cshtml`: |
||||
|
```html |
||||
|
<div class="text-light mt-1"> |
||||
|
<button class="btn text-light" href="#" id="ToolbarChangeTheme"> |
||||
|
<i class="fas fa-sun"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
``` |
||||
|
|
||||
|
- Create a new file named `Components/ChangeTheme/ChangeThemeViewComponent.cs`: |
||||
|
```csharp |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
|
||||
|
namespace BasicThemeDarkMode.Web.Components.ChangeTheme; |
||||
|
|
||||
|
[Widget(ScriptFiles = new[]{"/Components/ChangeTheme/ChangeTheme.js"})] |
||||
|
public class ChangeThemeViewComponent : AbpViewComponent |
||||
|
{ |
||||
|
public IViewComponentResult Invoke() |
||||
|
{ |
||||
|
return View("~/Components/ChangeTheme/Default.cshtml"); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
- Create a JavaScript that manages the last selected theme and toggles the color mode. It stores the last selected theme in the *local storage*. So, you don't need to store it in the database. |
||||
|
|
||||
|
- Create a new file named `Components/ChangeTheme/ChangeTheme.js`: |
||||
|
```js |
||||
|
$(function () { |
||||
|
function changeTheme(theme) { |
||||
|
window.localStorage.setItem('theme', theme); |
||||
|
document.getElementsByTagName('body')[0].setAttribute('data-bs-theme', theme); |
||||
|
} |
||||
|
|
||||
|
function toggleTheme(){ |
||||
|
getTheme() == 'light' ? changeTheme('dark') : changeTheme('light'); |
||||
|
} |
||||
|
|
||||
|
function getTheme(){ |
||||
|
return window.localStorage.getItem('theme') ?? 'dark'; |
||||
|
} |
||||
|
|
||||
|
function init(){ |
||||
|
let theme = getTheme(); |
||||
|
if(theme){ |
||||
|
changeTheme(theme); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
document.getElementById('ToolbarChangeTheme').addEventListener('click', () => { |
||||
|
toggleTheme(); |
||||
|
}); |
||||
|
|
||||
|
init(); |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
3. Create a new [Toolbar Contributor](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Toolbars) and add a newly created view component to the application toolbar. |
||||
|
|
||||
|
- Create a new class named `BasicThemeDarkModeToolbarContributor.cs`: |
||||
|
```csharp |
||||
|
using BasicThemeDarkMode.Web.Components.ChangeTheme; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Toolbars; |
||||
|
|
||||
|
namespace BasicThemeDarkMode.Web; |
||||
|
|
||||
|
public class BasicThemeDarkModeToolbarContributor : IToolbarContributor |
||||
|
{ |
||||
|
public Task ConfigureToolbarAsync(IToolbarConfigurationContext context) |
||||
|
{ |
||||
|
if (context.Toolbar.Name == StandardToolbars.Main) |
||||
|
{ |
||||
|
context.Toolbar.Items |
||||
|
.Add(new ToolbarItem(typeof(ChangeThemeViewComponent))); |
||||
|
} |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
- Configure [Toolbar Options](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Toolbars) and add a newly created contributor: |
||||
|
```csharp |
||||
|
Configure<AbpToolbarOptions>(options => |
||||
|
{ |
||||
|
options.Contributors.Add(new BasicThemeDarkModeToolbarContributor()); |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
That's it! Now, you can toggle the color mode by clicking the sun icon in the toolbar: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
- Users Page in Dark Mode: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
- Settings Page in Dark Mode: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
- Login Page in Dark Mode: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
## Conclusion |
||||
|
|
||||
|
The theme is stored in **local storage** and it's initialized on the client-side. You can use **Cookies** to render the page in the last selected theme on **server-side** to prevent the flash effect while navigating pages. This document shows the concept of adding dark mode support of bootstrap to the Basic Theme. |
||||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 189 KiB |
@ -0,0 +1,148 @@ |
|||||
|
# ABP Commercial - GDPR Module Overview |
||||
|
|
||||
|
In this article, I will highlight ABP Commercial's [GDPR Module](https://commercial.abp.io/modules/Volo.Gdpr) and show you how to provide personal data to the GDPR Module that is collected by your application and make it aligned with personal data download results. |
||||
|
|
||||
|
## GDPR Module |
||||
|
|
||||
|
[GDPR Module](https://docs.abp.io/en/commercial/latest/modules/gdpr) allows users to download and delete the data collected by the application. It's used for Personal Data Management obligating by the GDPR regulation and provides the following features: |
||||
|
|
||||
|
* Allows users to request their personal data. |
||||
|
* Lists the personal data requests and users can download their personal data by the listed request times. |
||||
|
* Allows users to delete their personal data and their accounts permanently. |
||||
|
|
||||
|
The GDPR module is pre-installed in the [Application](https://docs.abp.io/en/commercial/latest/startup-templates/application/index) and [Application (Single Layer)](https://docs.abp.io/en/commercial/latest/startup-templates/application-single-layer/index) Pro Startup templates for ABP Commercial customers. Therefore, most of the time you don't need to manually install it. |
||||
|
|
||||
|
> If you need to install the GDPR Module manually, you can refer to the [GDPR Module documentation](https://docs.abp.io/en/commercial/latest/modules/gdpr#how-to-install). |
||||
|
|
||||
|
Let's create an Application template with the following command and see the UI for the GDPR module: |
||||
|
|
||||
|
```csharp |
||||
|
abp new GdprDemo -t app-pro |
||||
|
``` |
||||
|
|
||||
|
After the application is created, we can run the application and login to the application. Then, when we check the user menu, we should be able to see the "Personal Data" menu item: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
We can click this menu item to navigate to the "Personal Data Management" page: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
This page is used to manage personal data requests. You can view past requests, the current status of the latest request, create a new personal data download request, download the prepared personal data or delete all your personal data (makes the data anonymized) and account from the application. |
||||
|
|
||||
|
### Downloading Personal Data |
||||
|
|
||||
|
After a quick overview of the GDPR module, now we can deep dive into the Personal Data Download process. |
||||
|
|
||||
|
The GDPR module requests information from the other modules that reference the `Volo.Abp.Gdpr.Abstractions` package and merges the response data into a single JSON file and the personal data can be downloaded later by the user from the "Personal Data Management" page when it's prepared (when the specified preparation time has been passed). |
||||
|
|
||||
|
Currently, only the [Identity Pro module](https://docs.abp.io/en/commercial/latest/modules/identity) provides some personal data to the GDPR Module because it's the only module that can be considered as collecting personal data, such as the user's name, surname, email address, and so on. Therefore, ABP makes everything that is needed such as getting all data collected when the personal data download request has been made or deleting/anonymizing the personal data when the personal data delete request has been made. |
||||
|
|
||||
|
This might be enough for some of your applications and you would not need to collect any other sensitive data from a user. However, most of the time you would probably need to collect some personal data from users for certain reasons. |
||||
|
|
||||
|
In that case, you should provide the personal data that you collected for a certain user in your application/module or provider to the GPDR module. So, when the personal data download request has been made, the GDPR module can request the information from the different part of your application and merges the response data into a single JSON file to be downloaded. In other words, the data provided by you will be considered as personal data and whenever a user requests his/her personal data, it'll be included in the result. |
||||
|
|
||||
|
In the next section, we will see how to provide personal data from a different module/provider. |
||||
|
|
||||
|
## Providing Personal Data |
||||
|
|
||||
|
Let's assume that we have an e-commerce application and we need to get the address from a user to send their order. Address information is considered as personal data according to GDPR regulations. Therefore, we need to provide the address information to the GDPR Module. |
||||
|
|
||||
|
For this purpose, we need to subscribe to the `GdprUserDataDeletionRequestedEto` and `GdprUserDataRequestEto` distributed events. These ETOs (event transfer objects) come from the `Volo.Abp.Gdpr.Abstractions` package, which is used to start deleting/anonymizing and downloading personal data requests. `Volo.Abp.Gdpr.Domain` package is pre-installed in the domain layer and has reference to that package, so most of the time you don't need to reference to the `Volo.Abp.Gdpr.Abstractions` package. |
||||
|
|
||||
|
But, if you want to use these ETOs in a different project, you should add the `Volo.Abp.Gdpr.Abstractions` package. You can use the following command to add it to your project: |
||||
|
|
||||
|
```bash |
||||
|
abp add-package Volo.Abp.Gdpr.Abstractions |
||||
|
``` |
||||
|
|
||||
|
After we have ensured that we have reference to the package, then we can create a new event handler class named `AddressGdprEventHandler` and subscribe to the `GdprUserDataDeletionRequestedEto` and `GdprUserDataRequestEto` distributed events: |
||||
|
|
||||
|
```csharp |
||||
|
public class AddressGdprEventHandler : |
||||
|
IDistributedEventHandler<GdprUserDataDeletionRequestedEto>, |
||||
|
IDistributedEventHandler<GdprUserDataRequestedEto>, |
||||
|
ITransientDependency |
||||
|
{ |
||||
|
//inject your services |
||||
|
protected IDistributedEventBus EventBus { get; } |
||||
|
|
||||
|
public AddressGdprEventHandler(IDistributedEventBus eventBus) |
||||
|
{ |
||||
|
EventBus = eventBus; |
||||
|
} |
||||
|
|
||||
|
public Task HandleEventAsync(GdprUserDataDeletionRequestedEto eventData) |
||||
|
{ |
||||
|
//anonymize the personal data |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public async Task HandleEventAsync(GdprUserDataRequestedEto eventData) |
||||
|
{ |
||||
|
//assume we have retrieved the address for a certain user |
||||
|
//by querying eventData.UserId |
||||
|
var address = new Address |
||||
|
{ |
||||
|
City = "Istanbul", |
||||
|
Postcode = "...", |
||||
|
|
||||
|
//other properties... |
||||
|
}; |
||||
|
|
||||
|
var gdprDataInfo = new GdprDataInfo |
||||
|
{ |
||||
|
{ "City", address.City }, |
||||
|
{ "Postcode", address.Postcode } |
||||
|
}; |
||||
|
|
||||
|
await EventBus.PublishAsync( |
||||
|
new GdprUserDataPreparedEto |
||||
|
{ |
||||
|
RequestId = eventData.RequestId, |
||||
|
Data = gdprDataInfo, //collected personal data |
||||
|
Provider = GetType().FullName //your module/provider name |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
* Here, we have subscribed to the `GdprUserDataDeletionRequestedEto` and `GdprUserDataRequestEto` distributed events. |
||||
|
* When a user made a personal data preparation request, then the `GdprUserDataRequestEto` is published by the GDPR module and the data preparation process starts. Modules/providers provide sensitive personal data by subscribing to this event and publishing a `GdprUserDataPreparedEto` distributed event, with the data and the provider information. Thanks to this, you can distinguish the personal data by the collected provider/module. |
||||
|
* Then, the GDPR module subscribes to the `GdprUserDataPreparedEto` distributed events that are published by multiple modules/providers and merge the response data into a single JSON file, and the personal data can be downloaded later by the user when the data preparation is done. |
||||
|
* On the other hand, when a user made a request to delete his/her personal data from the application, the `GdprUserDataDeletionRequestedEto` is published, so we can subscribe to the event and anonymize the personal data in our application. |
||||
|
* Also, the Identity Pro module deletes the user's account permanently. |
||||
|
|
||||
|
Now, let's run the application once again and make a personal data download request. But before that, we can configure the `AbpGdprOptions` as follows to not wait for long and show our personal data result: |
||||
|
|
||||
|
```csharp |
||||
|
Configure<AbpGdprOptions>(options => |
||||
|
{ |
||||
|
options.MinutesForDataPreparation = 5; //wait 5 minutes for data-preperation |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
After we have made a personal data preparation request, we need to wait for 5 minutes for personal data to be collected and then we can download it. After 5 minutes have passed, we can see the "Download" action button for the request on the "Personal Data Management" page: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Then, when we clicked the download button, a zip file will be downloaded. We can unzip the file and examine the JSON files that contain our personal data. If you have followed and applied the steps from now, you should be seeing two JSON files. One of them is for user-specific information provided by the Identity Pro module and the other one is the address information provided by us (our application-specific personal data). |
||||
|
|
||||
|
 |
||||
|
|
||||
|
*Basic information for the user:* |
||||
|
|
||||
|
```json |
||||
|
{"Username":"admin","Name":"admin","Surname":null,"Email":"admin@abp.io","Phone Number":null} |
||||
|
``` |
||||
|
|
||||
|
*Address information that we provided in this article:* |
||||
|
|
||||
|
```json |
||||
|
{"City":"Istanbul","Postcode":"..."} |
||||
|
``` |
||||
|
|
||||
|
## Conclusion |
||||
|
|
||||
|
In this article, I've explained the GDPR Module's data collection system and given you a brief overview of the module. GDPR Module allows you to request to download personal data, delete/anonymize your own personal data and delete your account permanently. It's pre-installed in the Application and Application(single layer) Pro Startup templates, but you can easily configure it to your existing application. |
||||
|
After Width: | Height: | Size: 413 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
@ -0,0 +1,94 @@ |
|||||
|
# ABP Version 7.4 Migration Guide |
||||
|
|
||||
|
This document is a guide for upgrading ABP v7.3 solutions to ABP v7.4. There are a few changes in this version that may affect your applications, please read it carefully and apply the necessary changes to your application. |
||||
|
|
||||
|
## Renamed the `AddGlobalFilters<>` method as `FilterQueryable<>` in `IMongoDbRepositoryFilterer` |
||||
|
|
||||
|
ABP Framework provides services to automatically filter data on querying from a database. Prior to this version, creating a new class that derives from the `MongoDbRepositoryFilterer` and overriding its `AddGlobalFilters` method was needed for implementing a data filter for [MongoDB](../MongoDB.md). |
||||
|
|
||||
|
In this version, the `AddGlobalFilters<>` method is renamed as `FilterQueryable<>`. Therefore, you need to update the method name if you have used data filtering for MongoDB, in your application. |
||||
|
|
||||
|
## Exposing Integration Services |
||||
|
|
||||
|
[Integration Services](../Integration-Services.md) are now not being exposed by default. In a monolith application, integration services don't need to be exposed outside since the modules would probably be in-process communication with each other. Therefore, they don't need to be exposed for most of the time. |
||||
|
|
||||
|
If you build a microservice solution or you need to access an integration service via a network call from any other application, you will probably need to expose the integration services so the other applications can consume them. |
||||
|
|
||||
|
To expose integration services and controllers, you can configure the `AbpAspNetCoreMvcOptions` and set the `ExposeIntegrationServices` property as *true* in the `ConfigureServices` method of your [module class](../Module-Development-Basics.md): |
||||
|
|
||||
|
```csharp |
||||
|
Configure<AbpAspNetCoreMvcOptions>(options => |
||||
|
{ |
||||
|
options.ExposeIntegrationServices = true; |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
## `LocalizationResource` property removed from the `TemplateDefinition` class |
||||
|
|
||||
|
In this version, the `LocalizationResource` property was removed from the `TemplateDefinition` class and instead, the `LocalizationResourceName` property has been added. |
||||
|
|
||||
|
```diff |
||||
|
- public Type LocalizationResource { get; set; } |
||||
|
+ public string LocalizationResourceName { get; set; } |
||||
|
``` |
||||
|
|
||||
|
## Changed the method signature for `ICorrelationIdProvider.Get()` |
||||
|
|
||||
|
Prior to this version, the `ICorrelationIdProvider.Get()` method used to return a non nullable string that represented a *correlationId* (a unique key that is used in distributed applications to trace requests across multiple services/operations). In this version, this method may return `null` if it hasn't been generated by `AbpCorrelationIdMiddleware` before. |
||||
|
|
||||
|
```diff |
||||
|
public interface ICorrelationIdProvider |
||||
|
{ |
||||
|
|
||||
|
- [NotNull] string Get(); |
||||
|
+ string? Get(); |
||||
|
|
||||
|
//other methods |
||||
|
|
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Therefore, if you've used this method in your application, you might want to make a null check and update the method signature where it's used. |
||||
|
|
||||
|
> See [#16795](https://github.com/abpframework/abp/pull/16795) for more information. |
||||
|
|
||||
|
## Dynamic Setting Store - Setting Management Module |
||||
|
|
||||
|
In this version, ABP Framework introduces Dynamic Setting Store, which is an important feature that allows us to collect and get all setting definitions from a single point. This feature requires some actions that need to be taken care of as the following: |
||||
|
|
||||
|
* You need to create a new migration and apply it to your database because a new database table has been added. |
||||
|
* `ISettingDefinitionManager`'s sync methods have been removed and instead, asynchronous versions of the existing methods have been added. |
||||
|
|
||||
|
```diff |
||||
|
public interface ISettingDefinitionManager |
||||
|
{ |
||||
|
- SettingDefinition Get([NotNull] string name); |
||||
|
+ Task<SettingDefinition> GetAsync([NotNull] string name); |
||||
|
|
||||
|
- IReadOnlyList<SettingDefinition> GetAll(); |
||||
|
+ Task<IReadOnlyList<SettingDefinition>> GetAllAsync(); |
||||
|
|
||||
|
- SettingDefinition? GetOrNull(string name); |
||||
|
+ Task<SettingDefinition?> GetOrNullAsync([NotNull] string name); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## `IdentityUserIntegrationService` - Identity Module |
||||
|
|
||||
|
In this version, ABP Framework introduces the `IdentityUserIntegrationService`, which is designed to get the current user's information, such as his/her role names within a non-authorized integration service. |
||||
|
|
||||
|
> For more information, see the related PR: [#16962](https://github.com/abpframework/abp/pull/16962) |
||||
|
|
||||
|
This is a breaking change for microservice solutions because of the following two reasons and it should be considered: |
||||
|
|
||||
|
* `IdentityUserIntegrationService` provides non-authorized services. This is not breaking the application, but should be taken care of. Since, everyone can use the service to retrieve some information for a certain user (for example, the role names of a user). |
||||
|
* Secondly, since integration services are not exposed by default anymore as explained in the *Exposing Integration Services* section above, you should explicitly enable exposing integration services. Otherwise, the operation will fail and you'll get a `404` error from the identity microservice. |
||||
|
|
||||
|
To expose integration services and controllers, you can configure the `AbpAspNetCoreMvcOptions` and set the `ExposeIntegrationServices` property as *true* in the `ConfigureServices` method of your [module class](../Module-Development-Basics.md): |
||||
|
|
||||
|
```csharp |
||||
|
Configure<AbpAspNetCoreMvcOptions>(options => |
||||
|
{ |
||||
|
options.ExposeIntegrationServices = true; |
||||
|
}); |
||||
|
``` |
||||
@ -0,0 +1,80 @@ |
|||||
|
# Caps Lock Directive |
||||
|
|
||||
|
In password inputs, You may want to show if Caps Lock is on. To make this even easier, you can use the `TrackCapsLockDirective` which has been exposed by the `@abp/ng.core` package. |
||||
|
|
||||
|
|
||||
|
## Getting Started |
||||
|
|
||||
|
`TrackCapsLockDirective` is standalone. In order to use the `TrackCapsLockDirective` in an HTML template, import it to related module or your standalone component: |
||||
|
|
||||
|
**Importing to NgModule** |
||||
|
```ts |
||||
|
import { TrackCapsLockDirective } from '@abp/ng.core'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
//... |
||||
|
declarations: [ |
||||
|
..., |
||||
|
TestComponent |
||||
|
], |
||||
|
imports: [ |
||||
|
..., |
||||
|
TrackCapsLockDirective |
||||
|
], |
||||
|
}) |
||||
|
export class MyFeatureModule {} |
||||
|
``` |
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
The `TrackCapsLockDirective` is very easy to use. The directive's selector is **`abpCapsLock`**. By adding the `abpCapsLock` event to an element, you can track the status of Caps Lock. You can use this to warn user. |
||||
|
|
||||
|
See an example usage: |
||||
|
|
||||
|
**NgModule Component usage** |
||||
|
```ts |
||||
|
@Component({ |
||||
|
selector: 'test-component', |
||||
|
template: ` |
||||
|
<div class="d-flex flex-column"> |
||||
|
<label>Password</label> |
||||
|
<input (abpCapsLock)="capsLock = $event"/> |
||||
|
<i *ngIf="capsLock">icon</i> |
||||
|
</div> |
||||
|
` |
||||
|
}) |
||||
|
export class TestComponent{ |
||||
|
capsLock = false; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**Standalone Component usage** |
||||
|
```ts |
||||
|
import { TrackCapsLockDirective } from '@abp/ng.core' |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'standalone-component', |
||||
|
standalone: true, |
||||
|
template: ` |
||||
|
<div class="d-flex flex-column"> |
||||
|
<label>Password</label> |
||||
|
<input (abpCapsLock)="capsLock = $event"/> |
||||
|
<i *ngIf="capsLock">icon</i> |
||||
|
</div> |
||||
|
`, |
||||
|
imports: [TrackCapsLockDirective] |
||||
|
}) |
||||
|
export class StandaloneComponent{ |
||||
|
capsLock = false; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
The `abpCapsLock` event has been added to the `<input>` element. Press Caps Lock to activate the `TrackCapsLockDirective`. |
||||
|
|
||||
|
See the result: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
To see Caps Lock icon press Caps Lock. |
||||
|
|
||||
|
 |
||||
@ -0,0 +1,30 @@ |
|||||
|
# Internet Connection Service |
||||
|
`InternetConnectionService` is a service which is exposed by the `@abp/ng.core` package. **You can use this service in order to check your internet connection** |
||||
|
|
||||
|
## Getting Started |
||||
|
When you inject the InternetConnectionService you can get the current internet status, and it gets immediately updated if the status changes. |
||||
|
|
||||
|
`InternetConnectionService` provides two choices to catch the network status: |
||||
|
1. Signal (readonly) |
||||
|
2. Observable |
||||
|
|
||||
|
|
||||
|
# How To Use |
||||
|
İt's easy, just inject the service and get the network status. |
||||
|
|
||||
|
**You can get via signal** |
||||
|
```ts |
||||
|
class SomeComponent{ |
||||
|
internetConnectionService = inject(InternetConnectionService); |
||||
|
isOnline = this.internetConnectionService.networkStatus |
||||
|
} |
||||
|
``` |
||||
|
**or you can get the observable** |
||||
|
```ts |
||||
|
class SomeComponent{ |
||||
|
internetConnectionService = inject(InternetConnectionService); |
||||
|
isOnline = this.internetConnectionService.networkStatus$ |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
To see how we implement to the template, check the `InternetConnectionStatusComponent` |
||||
@ -0,0 +1,77 @@ |
|||||
|
# Show Password Directive |
||||
|
|
||||
|
In password input, text can be shown easily via changing input type attribute to `text`. To make this even easier, you can use the `ShowPasswordDirective` which has been exposed by the `@abp/ng.core` package. |
||||
|
|
||||
|
|
||||
|
## Getting Started |
||||
|
`ShowPasswordDirective` is standalone. In order to use the `ShowPasswordDirective` in an HTML template, import it to related module or your standalone component: |
||||
|
|
||||
|
**Importing to NgModule** |
||||
|
```ts |
||||
|
import { ShowPasswordDirective } from '@abp/ng.core'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
//... |
||||
|
declarations: [ |
||||
|
..., |
||||
|
TestComponent |
||||
|
], |
||||
|
imports: [ |
||||
|
..., |
||||
|
ShowPasswordDirective |
||||
|
], |
||||
|
}) |
||||
|
export class MyFeatureModule {} |
||||
|
``` |
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
The `ShowPasswordDirective` is very easy to use. The directive's selector is **`abpShowPassword`**. By adding the `abpShowPassword` attribute to an input element, you can activate the `ShowPasswordDirective` for the input element. |
||||
|
|
||||
|
See an example usage: |
||||
|
|
||||
|
**NgModule Component usage** |
||||
|
```ts |
||||
|
@Component({ |
||||
|
selector: 'test-component', |
||||
|
template: ` |
||||
|
<div class="d-flex flex-column"> |
||||
|
<label>Password</label> |
||||
|
<input [abpShowPassword]="showPassword"/> |
||||
|
<i (click)="showPassword = !showPassword">icon</i> |
||||
|
</div> |
||||
|
` |
||||
|
}) |
||||
|
export class TestComponent{ |
||||
|
showPassword = false; |
||||
|
} |
||||
|
``` |
||||
|
**Standalone Component usage** |
||||
|
```ts |
||||
|
import { ShowPasswordDirective } from '@abp/ng.core'; |
||||
|
@Component({ |
||||
|
selector: 'standalone-component', |
||||
|
standalone: true, |
||||
|
template: ` |
||||
|
<div class="d-flex flex-column"> |
||||
|
<label>Password</label> |
||||
|
<input [abpShowPassword]="showPassword"/> |
||||
|
<i (click)="showPassword = !showPassword">icon</i> |
||||
|
</div> |
||||
|
`, |
||||
|
imports: [ShowPasswordDirective] |
||||
|
}) |
||||
|
export class StandaloneComponent{ |
||||
|
showPassword = false; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
The `abpShowPassword` attribute has been added to the `<input>` element. Click icon to activate the `ShowPasswordDirective`. |
||||
|
|
||||
|
See the result: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
To see password input click icon. |
||||
|
|
||||
|
 |
||||
@ -0,0 +1,46 @@ |
|||||
|
# Sorting Navigation Elements |
||||
|
This documentation describes how the navigation elements are sorted and how to change this default behaviour. |
||||
|
|
||||
|
When you want to add the `Navigation Element` you can use the `RoutesService`. For more details, see the [document](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu#how-to-add-a-navigation-element). |
||||
|
However, in this documentation, we will talk more about how to sort the navigation elements with the `order` attribute from the `Routes Service`. |
||||
|
|
||||
|
|
||||
|
### Order Property |
||||
|
- This parameter is optional and is used for sorting purposes. |
||||
|
- If you define this property it will be sorted by the default sorting function. |
||||
|
- You can edit this function. |
||||
|
|
||||
|
**Default Compare Function;** |
||||
|
``compare-func.token.ts`` |
||||
|
```ts |
||||
|
export const SORT_COMPARE_FUNC = new InjectionToken<0 | 1 | -1>('SORT_COMPARE_FUNC'); |
||||
|
|
||||
|
export function compareFuncFactory() { |
||||
|
const localizationService = inject(LocalizationService); |
||||
|
const fn = (a,b) => { |
||||
|
const aName = localizationService.instant(a.name); |
||||
|
const bName = localizationService.instant(b.name); |
||||
|
|
||||
|
const aNumber = a.order; |
||||
|
const bNumber = b.order; |
||||
|
|
||||
|
if (!Number.isInteger(aNumber)) return 1; |
||||
|
if (!Number.isInteger(bNumber)) return -1; |
||||
|
|
||||
|
if (aNumber > bNumber) return 1 |
||||
|
if (aNumber < bNumber) return -1 |
||||
|
|
||||
|
if (aName > bName ) return 1; |
||||
|
if (aName < bName ) return -1; |
||||
|
|
||||
|
return 0 |
||||
|
} |
||||
|
return fn; |
||||
|
} |
||||
|
``` |
||||
|
**What does this function do?** |
||||
|
- if the order property is defined, then it will be sorted by the order value. |
||||
|
- if both of the navigation elements have the same order value then it will be sorted by the name. |
||||
|
- If the order property is not defined, it will be the last element and the unordered navs will be sorted by name. |
||||
|
|
||||
|
You can edit this sorting function behaviour as you wish. |
||||
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.8 KiB |