diff --git a/en/Images/user-action-extension-click-me-ng.png b/en/Images/user-action-extension-click-me-ng.png new file mode 100644 index 00000000..675f0521 Binary files /dev/null and b/en/Images/user-action-extension-click-me-ng.png differ diff --git a/en/Images/user-page-toolbar-extension-click-me-ng.png b/en/Images/user-page-toolbar-extension-click-me-ng.png new file mode 100644 index 00000000..fcd36e4a Binary files /dev/null and b/en/Images/user-page-toolbar-extension-click-me-ng.png differ diff --git a/en/Images/user-page-toolbar-extension-custom-click-me-ng.png b/en/Images/user-page-toolbar-extension-custom-click-me-ng.png new file mode 100644 index 00000000..d8fd054e Binary files /dev/null and b/en/Images/user-page-toolbar-extension-custom-click-me-ng.png differ diff --git a/en/abp-suite/generating-crud-page.md b/en/abp-suite/generating-crud-page.md index 6cebd459..0fc9a776 100644 --- a/en/abp-suite/generating-crud-page.md +++ b/en/abp-suite/generating-crud-page.md @@ -107,6 +107,59 @@ You can use sorting field column to specify or change the order in which results ![Property list](../images/suite-list-properties.png) +## Navigation Properties + +A **navigation property** is an optional **property** on an entity type that allows for **navigation** from one end of an association to the other end. Unlike other properties, **navigation properties** do not carry data. + +Here's a navigation property example; The `student` entity holds a foreign key to the `teacher` entity which stores the primary key of the `teacher` entity. + +![Navigation property example](../images/suite-entity-with-navigation-property.png) + +#### How to define a navigation property? + +* **Entity namespace**: Namespace of the **target entity** you want to use as the navigation property. + * Example:`SampleMongoSchoolApp.Teachers` +* **Entity name**: Name of the **target entity**. + * Example: `Teacher` +* **Entity collection name**: Collection name of the **target entity** in `DbContext`. + * Example: `Teachers` +* **DTO namespace**: Namespace of DTO object of the entity. + * Example:`SampleMongoSchoolApp.Teachers` +* **DTO name**: Name of the DTO object of the **target entity**. + * Example:`TeacherDto` +* **Primary key**: Primary key type of the **target entity**. + * Example: `Guid` +* **Property name**: Name of the (reference) property that will be created in the main entity. + * Example: `TeacherId` +* **Display property**: Property name of the target entity which you want to show as display text. It must be a string property. + * Example: `Name` +* **UI pick type**: Determinates how to set the navigation value. + * **Modal**: In a modal window, it opens the target entity list and allows you to select one. Recommended for entities with large data. + * **Dropdown**: A dropdown will allow you to select a navigation property. Recommended for entities with fewer data. + +In this example scenario, we will create a `Student` entity and add a navigation property to the already existing entity `Teacher`. In the end, the user will be able select a teacher for a student. +See the screenshot that shows you how to do this: + +![Navigation property example](../images/suite-navigation-property-create-new.png) + +### Saving an entity + +There are 2 options to save an entity. + +#### Save + +Saves only the entity as draft and doesn't generate code. This is useful when you don't want to apply changes to your project. + +#### Save and generate + +Saves the entity and generates code. Your project will be added a new CRUD page. + +### Database table + +When you click **Save and generate** button it'll create all the related objects. The below screenshot is the MS SQL database table that's generated via ABP Suite. + +![Database table for the new entity](../images/suite-database-table.png) + ## User interface ### New book dialog diff --git a/en/docs-nav.json b/en/docs-nav.json index a7fdf542..bdc24a3d 100644 --- a/en/docs-nav.json +++ b/en/docs-nav.json @@ -107,6 +107,10 @@ { "text": "Twilio SMS", "path": "modules/twilio-sms.md" + }, + { + "text": "Payment", + "path": "modules/payment.md" } ] }, diff --git a/en/guides/customizing-modules.md b/en/guides/customizing-modules.md index a2799a65..5daf6d50 100644 --- a/en/guides/customizing-modules.md +++ b/en/guides/customizing-modules.md @@ -12,7 +12,9 @@ ABP Commercial adds **more extension points** on top of the ABP Framework. > This section **only focuses on the additional features** provided by the ABP Commercial. You should definitely see the guide mentioned in the previous section. -### Entity Actions +### User Interface + +#### Entity Actions Entity action extension system allows you to add a new action to the action menu for an entity. A "Click Me" action was added to the user management page below: @@ -23,4 +25,17 @@ You can take **any action** (open a modal, make an HTTP API call, redirect to an See the related documents to learn how to use this system: * [Entity Action Extensions for ASP.NET Core UI](../ui/aspnetcore/entity-action-extensions.md) -* [Entity Action Extensions for Angular](../ui/angular/entity-action-extensions.md) \ No newline at end of file +* [Entity Action Extensions for Angular](../ui/angular/entity-action-extensions.md) + +#### Page Toolbar + +Page toolbar system allows you to add components to the toolbar of any page. The page toolbar is the area right to the header of a page. A button ("Import users from excel") was added to the user management page below: + +![page-toolbar-button](../images/page-toolbar-button.png) + +You can add any type of view component item to the page toolbar or modify existing items. + +See the related documents to learn how to use this system: + +* [Page Toolbar Extensions for ASP.NET Core UI](../ui/aspnetcore/page-toolbar-extensions.md) +* [Page Toolbar Extensions for Angular](../ui/angular/page-toolbar-extensions.md) \ No newline at end of file diff --git a/en/images/page-toolbar-button.png b/en/images/page-toolbar-button.png new file mode 100644 index 00000000..05e3f9da Binary files /dev/null and b/en/images/page-toolbar-button.png differ diff --git a/en/images/page-toolbar-custom-component.png b/en/images/page-toolbar-custom-component.png new file mode 100644 index 00000000..a15ef485 Binary files /dev/null and b/en/images/page-toolbar-custom-component.png differ diff --git a/en/images/suite-entity-with-navigation-property.png b/en/images/suite-entity-with-navigation-property.png index 4e4c2934..de730f35 100644 Binary files a/en/images/suite-entity-with-navigation-property.png and b/en/images/suite-entity-with-navigation-property.png differ diff --git a/en/images/suite-example-navigation-entity-dto.png b/en/images/suite-example-navigation-entity-dto.png deleted file mode 100644 index 45712934..00000000 Binary files a/en/images/suite-example-navigation-entity-dto.png and /dev/null differ diff --git a/en/images/suite-example-navigation-entity.png b/en/images/suite-example-navigation-entity.png deleted file mode 100644 index 3f813f9d..00000000 Binary files a/en/images/suite-example-navigation-entity.png and /dev/null differ diff --git a/en/images/suite-navigation-property-create-new.png b/en/images/suite-navigation-property-create-new.png new file mode 100644 index 00000000..44a4592a Binary files /dev/null and b/en/images/suite-navigation-property-create-new.png differ diff --git a/en/images/suite-navigation-property-dbcontext-collection.png b/en/images/suite-navigation-property-dbcontext-collection.png deleted file mode 100644 index 28c0a432..00000000 Binary files a/en/images/suite-navigation-property-dbcontext-collection.png and /dev/null differ diff --git a/en/images/suite-navigation-property.png b/en/images/suite-navigation-property.png deleted file mode 100644 index 18361c48..00000000 Binary files a/en/images/suite-navigation-property.png and /dev/null differ diff --git a/en/images/suite-np-define.png b/en/images/suite-np-define.png deleted file mode 100644 index 62a4563f..00000000 Binary files a/en/images/suite-np-define.png and /dev/null differ diff --git a/en/modules/payment.md b/en/modules/payment.md index 64df84b9..b7fe3da1 100644 --- a/en/modules/payment.md +++ b/en/modules/payment.md @@ -119,6 +119,32 @@ Configure(options => * ```Recommended```: Is payment gateway is recommended or not. This information is displayed on payment gateway selection page. * ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. +### PayuOptions + +```PayuOptions``` is used to configure PayU payment gateway options. + +* ```Merchant```: Merchant code for PayU account. +* ```Signature```: Signature of Merchant. +* ```LanguageCode```: Language of the order. This will be used for notification email that are sent to the client, if available. +* ```CurrencyCode```: Currency code of order (USD, EUR, etc...). +* ```VatRate```: Vat rate of order. +* ```PriceType```: Price type of order (GROSS or NET). +* ```Shipping```: A positive number indicating the price of shipping. +* ```Installment```: The number of installments. It can be an integer between 1 and 12. +* ```TestOrder```: Is the order a test order or not (true or false). +* ```Debug```: Writes detailed log on PAYU side. +* ```Recommended```: Is payment gateway is recommended or not. This information is displayed on payment gateway selection page. +* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. + +### TwoCheckoutOptions + +* Signature: Signature of Merchant's 2Checkout account. +* CheckoutUrl: 2Checkout checkout URL (it must be set to https://secure.2checkout.com/order/checkout.php). +* ```LanguageCode```: Language of the order. This will be used for notification email that are sent to the client, if available. +* ```CurrencyCode```: Currency code of order (USD, EUR, etc...). +* ```Recommended```: Is payment gateway is recommended or not. This information is displayed on payment gateway selection page. +* ```ExtraInfos```: List of informative strings for payment gateway. These texts are displayed on payment gateway selection page. + Instead of configuring options in your module class, you can configure it in your appsettings.json file like below; ```json diff --git a/en/ui/angular/.prettierrc b/en/ui/angular/.prettierrc new file mode 100644 index 00000000..5393aaf2 --- /dev/null +++ b/en/ui/angular/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "arrowParens": "avoid" +} diff --git a/en/ui/angular/entity-action-extensions.md b/en/ui/angular/entity-action-extensions.md index 4677e43f..8b4e3021 100644 --- a/en/ui/angular/entity-action-extensions.md +++ b/en/ui/angular/entity-action-extensions.md @@ -4,13 +4,13 @@ Entity action extension system allows you to add a new action to the action menu for an entity. A "Click Me" action was added to the user management page below: -![user-action-extension-click-me](../../images/user-action-extension-click-me.png) +![Entity Action Extension Example: "Click Me!" Action](../../images/user-action-extension-click-me-ng.png) You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can access to the current entity in your code. ## How to Set Up -In this example, we will add a "Click Me!" action and execute a JavaScript code for the user management page of the [Identity Module](../../modules/identity.md). +In this example, we will add a "Click Me!" action and alert the current row's `userName` in the user management page of the [Identity Module](../../modules/identity.md). ### Step 1. Create Entity Action Contributors @@ -19,54 +19,56 @@ The following code prepares a constant named `identityActionContributors`, ready ```js // entity-action-contributors.ts -import { ActionList, EntityAction } from '@volo/abp.commercial.ng.ui'; +import { EntityAction, EntityActionList } from '@volo/abp.commercial.ng.ui'; import { Identity } from '@volo/abp.ng.identity'; -import { IdentityActionContributors } from '@volo/abp.ng.identity.config'; +import { IdentityEntityActionContributors } from '@volo/abp.ng.identity.config'; -const alertUserName = new EntityAction({ +const alertUserName = new EntityAction({ text: 'Click Me!', action: data => { // Replace alert with your custom code alert(data.record.userName); }, + // See EntityActionOptions in API section for all options }); -export function alertUserNameContributor(actionList: ActionList) { +export function alertUserNameContributor( + actionList: EntityActionList, +) { actionList.addTail(alertUserName); } -export const identityActionContributors: IdentityActionContributors = { - // 'Identity.UsersComponent' indicates where this action will be placed - 'Identity.UsersComponent': [ +export const identityEntityActionContributors: IdentityEntityActionContributors = { + // enum indicates the page to add contributors to + [eIdentityComponents.Users]: [ alertUserNameContributor, // You can add more contributors here ], }; ``` -The list of actions, conveniently named as `actionList`, is a **doubly linked list**. That is why we have used the `addTail` method, which adds the given value to the end of the list. You may find [all available methods here](../../Common/Utils/Linked-List). +The list of actions, conveniently named as `actionList`, is a **doubly linked list**. That is why we have used the `addTail` method, which adds the given value to the end of the list. You may find [all available methods here](https://docs.abp.io/en/abp/latest/UI/Common/Utils/Linked-List). > **Important Note:** AoT compilation does not support function calls in decorator metadata. This is why we have defined `alertUserNameContributor` as an exported function declaration here. Please do not forget exporting your contributor callbacks and forget about lambda functions (a.k.a. arrow functions). Please refer to [AoT metadata errors](https://angular.io/guide/aot-metadata-errors#function-calls-not-supported) for details. ### Step 2. Import and Use Entity Action Contributors -Import `identityActionContributors` in your root module and pass it to the static `forRoot` method of `IdentityConfigModule` as seen below: +Import `identityEntityActionContributors` in your root module and pass it to the static `forRoot` method of `IdentityConfigModule` as seen below: ```js import { IdentityConfigModule } from '@volo/abp.ng.identity.config'; -import { getIdentityActionContributors } from './entity-action-contributors'; +import { identityEntityActionContributors } from './entity-action-contributors'; @NgModule({ imports: [ // Other imports - + IdentityConfigModule.forRoot({ - entityActionContributors: identityActionContributors, + entityActionContributors: identityEntityActionContributors, }), - + // Other imports ], - providers: [], declarations: [AppComponent], bootstrap: [AppComponent], }) @@ -77,34 +79,59 @@ That is it, `alertUserName` entity action will be added as the last action on th ## API -### EntityData\ +### ActionData\ -`EntityData` is the shape of the parameter passed to all callbacks or predicates in an `EntityAction`. +`ActionData` is the shape of the parameter passed to all callbacks or predicates in an `EntityAction`. It has the following properties: -* **record** is the row data, i.e. current value rendered in the table. +- **record** is the row data, i.e. current value rendered in the table. + + ```js + { + text: 'Click Me!', + action: data => { + alert(data.record.userName); + }, + } + ``` -* **index** is the table index where the record is at. +- **index** is the table index where the record is at. -* **getInjected** is the equivalent of [Injector.get](https://angular.io/api/core/Injector#get). You can use it to reach injected dependencies of `GridActionsComponent`, including, but not limited to, its parent component. +- **getInjected** is the equivalent of [Injector.get](https://angular.io/api/core/Injector#get). You can use it to reach injected dependencies of `GridActionsComponent`, including, but not limited to, its parent component. ```js { text: 'Click Me!', action: data => { const restService = data.getInjected(RestService); - + // Use restService public props and methods here }, visible: data => { const usersComponent = data.getInjected(UsersComponent); - + // Use usersComponent public props and methods here }, } ``` +### ActionCallback\ + +`ActionCallback` is the type of the callback function that can be passed to an `EntityAction` as `action` parameter. An action callback gets a single parameter, the `ActionData`. The return type may be anything, including `void`. Here is a simplified representation: + +```js +type ActionCallback = (data?: ActionData) => any; +``` + +### ActionPredicate\ + +`ActionPredicate` is the type of the predicate function that can be passed to an `EntityAction` as `visible` parameter. An action predicate gets a single parameter, the `ActionData`. The return type must be `boolean`. Here is a simplified representation: + +```js +type ActionPredicate = (data?: ActionData) => boolean; +``` + ### EntityActionOptions\ `EntityActionOptions` is the type that defines required and optional properties you have to pass in order to create an entity action. @@ -113,21 +140,21 @@ Its type definition is as follows: ```js type EntityActionOptions = { - action: EntityActionCallback; - text: string; - icon?: string; - permission?: string; - visible?: EntityActionPredicate; -} + action: ActionCallback, + text: string, + icon?: string, + permission?: string, + visible?: ActionPredicate, +}; ``` As you see, passing `action` and `text` is enough to create an entity action. Here is what each property is good for: -* **action** is a callback that is called when the grid action is clicked. (_required_) -* **text** is the button text which will be localized. (_required_) -* **icon** is the classes that define an icon to be placed before the text. (_default:_ `''`) -* **permission** is the permission context which will be used to decide if this type of grid action should be displayed to the user or not. (_default:_ `undefined`) -* **visible** is a predicate that will be used to decide if the current record should have this grid action or not. (_default:_ `() => true`) +- **action** is a callback that is called when the grid action is clicked. (_required_) +- **text** is the button text which will be localized. (_required_) +- **icon** is the classes that define an icon to be placed before the text. (_default:_ `''`) +- **permission** is the permission context which will be used to decide if this type of grid action should be displayed to the user or not. (_default:_ `undefined`) +- **visible** is a predicate that will be used to decide if the current record should have this grid action or not. (_default:_ `() => true`) You may find a full example below. @@ -135,8 +162,8 @@ You may find a full example below. `EntityAction` is the class that defines your entity actions. It takes an `EntityActionOptions` and sets the default values to the properties, creating an entity action that can be passed to an entity contributor. -``` -const options: EntityActionOptions = { +```js +const options: EntityActionOptions = { action: data => { const component = data.getInjected(UsersComponent); component.unlock(data.record.id); @@ -144,30 +171,40 @@ const options: EntityActionOptions = { text: 'AbpIdentity::Unlock', icon: 'fa fa-unlock', permission: 'AbpIdentity.Users.Update', - visible: data => !data.record.isLockedOut, + visible: data => data.record.isLockedOut, }; -const action = new EntityAction(options); +const action = new EntityAction(options); ``` It also has two static methods to create its instances: -* **EntityAction.create\\(options: EntityActionOptions\\)** is used to create an instance of `EntityAction`. -* **EntityAction.createMany\\(options: EntityActionOptions\\[\]\)** is used to create multiple instances of `EntityAction` with given array of `EntityActionOptions`. +- **EntityAction.create\\(options: EntityActionOptions\\)** is used to create an instance of `EntityAction`. + ```js + const action = EntityAction.create(options); + ``` +- **EntityAction.createMany\\(options: EntityActionOptions\\[\]\)** is used to create multiple instances of `EntityAction` with given array of `EntityActionOptions`. + ```js + const actions = EntityAction.createMany(optionsArray); + ``` -### ActionList\ +### EntityActionList\ -`ActionList` is the list of actions passed to every action contributor callback as the first parameter named `actionList`. It is a **doubly linked list**. You may find [all available methods here](../../Common/Utils/Linked-List). +`EntityActionList` is the list of actions passed to every action contributor callback as the first parameter named `actionList`. It is a **doubly linked list**. You may find [all available methods here](https://docs.abp.io/en/abp/latest/UI/Common/Utils/Linked-List). -The items in the list will be displayed according to the liked list order, i.e. from head to tail. If you want to re-order them, all you have to do is something like this: +The items in the list will be displayed according to the linked list order, i.e. from head to tail. If you want to re-order them, all you have to do is something like this: ```js -export function reorderUserContributors(actionList: ActionList) { +export function reorderUserContributors( + actionList: EntityActionList, +) { + // drop "Unlock" button const unlockActionNode = actionList.dropByValue( 'AbpIdentity::Unlock', (action, text) => action.text === text, ); - + + // add it back to the head of the list actionList.addHead(unlockActionNode.value); } ``` @@ -177,30 +214,20 @@ export function reorderUserContributors(actionList: ActionList) { `EntityActionContributorCallback` is the type that you can pass as entity action contributor callbacks to static `forRoot` methods of the modules. ```js -// app.module.ts - -export function lockUserContributor(actionList: ActionList) { +export function lockUserContributor( + actionList: EntityActionList, +) { + // add lockUser as 3rd action actionList.add(lockUser).byIndex(2); - - // lockUser should have EntityActionContributorCallback type + + // lockUser should have EntityActionContributorCallback type } -@NgModule({ - imports: [ - // Other imports - - IdentityConfigModule.forRoot({ - entityActionContributors: { - 'Identity.UsersComponent': [ lockUserContributor ], - }, - }), - - // Other imports - ], - providers: [], - declarations: [AppComponent], - bootstrap: [AppComponent], -}) -export class AppModule {} +export const identityEntityActionContributors = { + [eIdentityComponents.Users]: [lockUserContributor], +}; ``` +## See Also + +- [Guide: Customizing the Modules](../../guides/customizing-modules.md) diff --git a/en/ui/angular/page-toolbar-extensions.md b/en/ui/angular/page-toolbar-extensions.md new file mode 100644 index 00000000..e572f7d5 --- /dev/null +++ b/en/ui/angular/page-toolbar-extensions.md @@ -0,0 +1,397 @@ +# Page Toolbar Extensions for Angular UI + +## Introduction + +Page toolbar extension system allows you to add a new action to the toolbar of a page. A "Click Me" action was added to the user management page below: + +![Page Toolbar Extension Example: "Click Me!" Action](../../images/user-page-toolbar-extension-click-me-ng.png) + +You can take any action (open a modal, make an HTTP API call, redirect to another page... etc) by writing your custom code. You can also access to page data (the main record, usually an entity list) in your code. Additionally, you can pass in custom components instead of using the default button. + +## How to Add an Action to Page Toolbar + +In this example, we will add a "Click Me!" action and log `userName` of all users in the user management page of the [Identity Module](../../modules/identity.md) to the console. + +### Step 1. Create Toolbar Action Contributors + +The following code prepares a constant named `identityToolbarActionContributors`, ready to be imported and used in your root module: + +```js +// toolbar-action-contributors.ts + +import { ToolbarActionList, ToolbarAction } from '@volo/abp.commercial.ng.ui'; +import { Identity } from '@volo/abp.ng.identity'; +import { IdentityToolbarActionContributors } from '@volo/abp.ng.identity.config'; + +const logUserNames = new ToolbarAction({ + text: 'Click Me!', + action: data => { + // Replace log with your custom code + data.record.forEach(user => console.log(user.userName)); + }, + // See ToolbarActionOptions in API section for all options +}); + +export function logUserNamesContributor( + actionList: ToolbarActionList +) { + actionList.addHead(logUserNames); +} + +export const identityToolbarActionContributors: IdentityToolbarActionContributors = { + // enum indicates the page to add contributors to + [eIdentityComponents.Users]: [ + logUserNamesContributor, + // You can add more contributors here + ], +}; + +``` + +The list of actions, conveniently named as `actionList`, is a **doubly linked list**. That is why we have used the `addHead` method, which adds the given value to the beginning of the list. You may find [all available methods here](https://docs.abp.io/en/abp/latest/UI/Common/Utils/Linked-List). + +> **Important Note:** AoT compilation does not support function calls in decorator metadata. This is why we have defined `logUserNamesContributor` as an exported function declaration here. Please do not forget exporting your contributor callbacks and forget about lambda functions (a.k.a. arrow functions). Please refer to [AoT metadata errors](https://angular.io/guide/aot-metadata-errors#function-calls-not-supported) for details. + +### Step 2. Import and Use Toolbar Action Contributors + +Import `identityToolbarActionContributors` in your root module and pass it to the static `forRoot` method of `IdentityConfigModule` as seen below: + +```js +import { IdentityConfigModule } from '@volo/abp.ng.identity.config'; +import { identityToolbarActionContributors } from './toolbar-action-contributors'; + +@NgModule({ + imports: [ + // Other imports + + IdentityConfigModule.forRoot({ + toolbarActionContributors: identityToolbarActionContributors, + }), + + // Other imports + ], + declarations: [AppComponent], + bootstrap: [AppComponent], +}) +export class AppModule {} +``` + +That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `IdentityModule`. + +## How to Add a Custom Component to Page Toolbar + +In this example, we will add a custom "Click Me!" button and log `userName` of all users in the user management page of the [Identity Module](../../modules/identity.md) to the console. + +### Step 1. Create A Custom Component + +We need to have a component before we can pass it to the toolbar action contributors: + +```js +// click-me-button.component.ts + +import { Component, Inject } from '@angular/core'; +import { ActionData, EXTENSIONS_ACTION_DATA } from '@volo/abp.commercial.ng.ui'; +import { Identity } from '@volo/abp.ng.identity'; + +@Component({ + selector: 'app-click-me-button', + template: ` + + `, +}) +export class ClickMeButtonComponent { + constructor( + @Inject(EXTENSIONS_ACTION_DATA) + private data: ActionData + ) {} + + handleClick() { + this.data.record.forEach(user => console.log(user.userName)); + } +} +``` + +Here, `EXTENSIONS_ACTION_DATA` token provides us the context from the page toolbar. Therefore, we are able to reach the page data via `record`, which is an array of users, i.e. `Identity.UserItem[]`. + +> We could also import `EXTENSIONS_ACTION_CALLBACK` from **@volo/abp.commercial.ng.ui** module, which is a higher order function that triggers the predefined `action` when called. It passes `ActionData` as the first parameter, so you do not have to pass it explicitly. In other words, `EXTENSIONS_ACTION_CALLBACK` can be called without any parameters and it will not fail. + +### Step 2. Create Toolbar Action Contributors + +The following code prepares a constant named `identityToolbarActionContributors`, ready to be imported and used in your root module. When `ToolbarComponent` is used instead of `ToolbarAction`, we can pass a component in: + +```js +// toolbar-action-contributors.ts + +import { ToolbarActionList, ToolbarComponent } from '@volo/abp.commercial.ng.ui'; +import { Identity } from '@volo/abp.ng.identity'; +import { IdentityToolbarActionContributors } from '@volo/abp.ng.identity.config'; +import { ClickMeButtonComponent } from './click-me-button.component'; + +const logUserNames = new ToolbarComponent({ + component: ClickMeButtonComponent, + // See ToolbarActionOptions in API section for all options +}); + +export function logUserNamesContributor( + actionList: ToolbarActionList +) { + actionList.addHead(logUserNames); +} + +export const identityToolbarActionContributors: IdentityToolbarActionContributors = { + // enum indicates the page to add contributors to + [eIdentityComponents.Users]: [ + logUserNamesContributor, + // You can add more contributors here + ], +}; + +``` + +The list of actions, conveniently named as `actionList`, is a **doubly linked list**. That is why we have used the `addHead` method, which adds the given value to the beginning of the list. You may find [all available methods here](https://docs.abp.io/en/abp/latest/UI/Common/Utils/Linked-List). + +> **Important Note:** AoT compilation does not support function calls in decorator metadata. This is why we have defined `logUserNamesContributor` as an exported function declaration here. Please do not forget exporting your contributor callbacks and forget about lambda functions (a.k.a. arrow functions). Please refer to [AoT metadata errors](https://angular.io/guide/aot-metadata-errors#function-calls-not-supported) for details. + +### Step 3. Import and Use Toolbar Action Contributors + +Import `identityToolbarActionContributors` in your root module and pass it to the static `forRoot` method of `IdentityConfigModule` as seen below. If Ivy is not enabled in your project, do not forget putting `ClickMeButtonComponent` into `entryComponents`: + +```js +import { IdentityConfigModule } from '@volo/abp.ng.identity.config'; +import { identityToolbarActionContributors } from './toolbar-action-contributors'; +import { ClickMeButtonComponent } from './click-me-button.component'; + +@NgModule({ + imports: [ + // Other imports + + IdentityConfigModule.forRoot({ + toolbarActionContributors: identityToolbarActionContributors, + }), + + // Other imports + ], + entryComponents: [ClickMeButtonComponent], // If not Ivy, do not forget this + declarations: [AppComponent, ClickMeButtonComponent], + bootstrap: [AppComponent], +}) +export class AppModule {} +``` + +That is it, `logUserNames` toolbar action will be added as the first action on the page toolbar in the users page (`UsersComponent`) of the `IdentityModule` and it will be triggered by a custom button, i.e. `ClickMeButtonComponent`. Please note that **component projection is not limited to buttons** and you may use other UI components. + +![Page Toolbar Extension Example: Custom "Click Me!" Button](../../images/user-page-toolbar-extension-custom-click-me-ng.png) + +## API + +### ActionData\ + +`ActionData` is the shape of the parameter passed to all callbacks or predicates in a `ToolbarAction`. + +It has the following properties: + +- **record** is the page data, the main record on a page, usually an entity list (e.g. list of users). + + ```js + { + text: 'Click Me!', + action: data => { + data.record.forEach(user => { + console.lof(user.userName); + }); + }, + } + ``` + +- **getInjected** is the equivalent of [Injector.get](https://angular.io/api/core/Injector#get). You can use it to reach injected dependencies of `PageToolbarComponent`, including, but not limited to, its parent component. + + ```js + { + text: 'Click Me!', + action: data => { + const restService = data.getInjected(RestService); + + // Use restService public props and methods here + }, + visible: data => { + const usersComponent = data.getInjected(UsersComponent); + + // Use usersComponent public props and methods here + }, + } + ``` + +### ActionCallback\ + +`ActionCallback` is the type of the callback function that can be passed to a `ToolbarAction` as `action` parameter. An action callback gets a single parameter, the `ActionData`. The return type may be anything, including `void`. Here is a simplified representation: + +```js +type ActionCallback = (data?: ActionData) => any; +``` + +### ActionPredicate\ + +`ActionPredicate` is the type of the predicate function that can be passed to a `ToolbarAction` as `visible` parameter. An action predicate gets a single parameter, the `ActionData`. The return type must be `boolean`. Here is a simplified representation: + +```js +type ActionPredicate = (data?: ActionData) => boolean; +``` + +### ToolbarActionOptions\ + +`ToolbarActionOptions` is the type that defines required and optional properties you have to pass in order to create an toolbar action. + +Its type definition is as follows: + +```js +type ToolbarActionOptions = { + action: ActionCallback, + text: string, + icon?: string, + permission?: string, + visible?: ActionPredicate, +}; +``` + +As you see, passing `action` and `text` is enough to create an toolbar action. Here is what each property is good for: + +- **action** is a callback that is called when the toolbar action is clicked. (_required_) +- **text** is the button text which will be localized. (_required_) +- **icon** is the classes that define an icon to be placed before the text. (_default:_ `''`) +- **permission** is the permission context which will be used to decide if this toolbar action should be displayed to the user or not. (_default:_ `undefined`) +- **visible** is a predicate that will be used to decide if the page toolbar should have this action or not. (_default:_ `() => true`) + +You may find a full example below. + +### ToolbarAction\ + +`ToolbarAction` is the class that defines your toolbar actions. It takes an `ToolbarActionOptions` and sets the default values to the properties, creating an toolbar action that can be passed to an toolbar contributor. + +```js +const options: ToolbarActionOptions = { + action: data => { + const service = data.getInjected(MyCustomIdentityService); + const lockedUsers = data.record.filter(user => user.isLockedOut); + service.unlockAll(lockedUsers); + }, + text: 'MyProjectName::UnlockAll', + icon: 'fa fa-unlock', + permission: 'AbpIdentity.Users.Update', + visible: data => data.record.some(user => user.isLockedOut), +}; + +const action = new ToolbarAction(options); +``` + +It also has two static methods to create its instances: + +- **ToolbarAction.create\\(options: ToolbarActionOptions\\)** is used to create an instance of `ToolbarAction`. + ```js + const action = ToolbarAction.create(options); + ``` +- **ToolbarAction.createMany\\(options: ToolbarActionOptions\\[\]\)** is used to create multiple instances of `ToolbarAction` with given array of `ToolbarActionOptions`. + +### ToolbarComponentOptions\ + +`ToolbarComponentOptions` is the type that defines required and optional properties you have to pass in order to create an toolbar component. + +Its type definition is as follows: + +```js +type ToolbarComponentOptions = { + component: Type, + action?: ActionCallback, + permission?: string, + visible?: ActionPredicate, +}; +``` + +As you see, passing `action` and `text` is enough to create an toolbar action. Here is what each property is good for: + +- **component** is the constructor of the component to be projected. (_required_) +- **action** is a predefined callback that you can reach in your component via `EXTENSIONS_ACTION_CALLBACK` token and trigger. (_optional_) +- **permission** is the permission context which will be used to decide if this toolbar action should be displayed to the user or not. (_default:_ `undefined`) +- **visible** is a predicate that will be used to decide if the page toolbar should have this action or not. (_default:_ `() => true`) + +You may find a full example below. + +### ToolbarComponent\ + +`ToolbarComponent` is the class that defines toolbar actions which project a custom component. It takes an `ToolbarComponentOptions` and sets the default values to the properties, creating a toolbar action that can be passed to an toolbar contributor. + +```js +const options: ToolbarComponentOptions = { + component: UnlockAllButton, + action: data => { + const service = data.getInjected(MyCustomIdentityService); + const lockedUsers = data.record.filter(user => user.isLockedOut); + service.unlockAll(lockedUsers); + }, + permission: 'AbpIdentity.Users.Update', + visible: data => data.record.some(user => user.isLockedOut), +}; + +const action = new ToolbarComponent(options); +``` + +It also has two static methods to create its instances: + +- **ToolbarComponent.create\\(options: ToolbarComponentOptions\\)** is used to create an instance of `ToolbarComponent`. + ```js + const action = ToolbarComponent.create(options); + ``` +- **ToolbarComponent.createMany\\(options: ToolbarComponentOptions\\[\]\)** is used to create multiple instances of `ToolbarComponent` with given array of `ToolbarComponentOptions`. + ```js + const actions = ToolbarComponent.createMany(optionsArray); + ``` + +### ToolbarActionList\ + +`ToolbarActionList` is the list of actions passed to every action contributor callback as the first parameter named `actionList`. It is a **doubly linked list**. You may find [all available methods here](https://docs.abp.io/en/abp/latest/UI/Common/Utils/Linked-List). + +The items in the list will be displayed according to the linked list order, i.e. from head to tail. If you want to re-order them, all you have to do is something like this: + +```js +export function reorderUserContributors( + actionList: ToolbarActionList, +) { + // drop "New User" button + const newUserActionNode = actionList.dropByValue( + 'AbpIdentity::NewUser', + (action, text) => action['text'] === text, + ); + + // add it back to the head of the list + actionList.addHead(newUserActionNode.value); +} + +export const identityEntityActionContributors = { + [eIdentityComponents.Users]: [ + logUserNamesContributor, + reorderUserContributors, + ], +}; +``` + +### ToolbarActionContributorCallback\ + +`ToolbarActionContributorCallback` is the type that you can pass as toolbar action contributor callbacks to static `forRoot` methods of the modules. + +```js +export function exportUsersContributor( + actionList: ToolbarActionList, +) { + // add exportUsers just before the last action + actionList.add(exportUsers).byIndex(-1); + + // exportUsers should have ToolbarActionContributorCallback type +} + +export const identityEntityActionContributors = { + [eIdentityComponents.Users]: [exportUsersContributor], +}; +``` + +## See Also + +- [Guide: Customizing the Modules](../../guides/customizing-modules.md) diff --git a/en/ui/aspnetcore/entity-action-extensions.md b/en/ui/aspnetcore/entity-action-extensions.md index 98066a8a..189977ec 100644 --- a/en/ui/aspnetcore/entity-action-extensions.md +++ b/en/ui/aspnetcore/entity-action-extensions.md @@ -20,7 +20,7 @@ First, add a new JavaScript file to your solution. We added inside the `/Pages/I Here, the content of this JavaScript file: -````js +```js var clickMeAction = { text: 'Click Me!', action: function(data) { @@ -34,7 +34,7 @@ abp.ui.extensions.entityActions .addContributor(function(actionList) { actionList.addTail(clickMeAction); }); -```` +``` In the `action` function, you can do anything you need. See the (*API*)[#api] section for detailed usage. @@ -44,7 +44,7 @@ Then you need to add this JavaScript file to the user management page. You can t Write the following code inside the `ConfigureServices` of your module class: -````csharp +```csharp Configure(options => { options.ScriptBundles.Configure( @@ -56,7 +56,7 @@ Configure(options => ); }); }); -```` +``` This configuration adds `my-user-extensions.js` to the user management page of the Identity Module. `typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel).FullName` is the name of the bundle in the user management page. This is a common convention used for all the ABP Commercial modules. @@ -72,7 +72,7 @@ This method is used to access the entity actions of a specific module. It takes ### abp.ui.extensions.entityActions.get(entityName).actions -The `actions` property is used to retrieve a [doubly liked list](../../Common/Utils/Linked-List) of previously defined actions for an entity. All contributors are executed in order to prepare the final actions list. This is normally called by the modules to show the actions in the grid. However, you can use it if you are building your own extensible UIs. +The `actions` property is used to retrieve a [doubly linked list](https://docs.abp.io/en/abp/latest/UI/Common/Utils/Linked-List) of previously defined actions for an entity. All contributors are executed in order to prepare the final actions list. This is normally called by the modules to show the actions in the grid. However, you can use it if you are building your own extensible UIs. ### abp.ui.extensions.entityActions.get(entityName).addContributor(contributeCallback) @@ -82,7 +82,7 @@ The `addContributor` method covers all scenarios, e.g. you want to add your acti #### Example -````js +```js var clickMe2Action = { text: 'Click Me 2!', icon: 'fas fa-hand-point-right', @@ -105,10 +105,10 @@ abp.ui.extensions.entityActions var lastAction = actionList.dropTail().value; actionList.addAfter(lastAction, clickMe2Action); }); -```` +``` > `actionList` is an object of type `ActionList` that is explained below. You can use its methods to build a list of actions however you need. ### abp.ui.extensions.ActionList -`ActionList` returns an instance of LinkedList class, a doubly linked list provided by the @abp/utils package. That instance is passed as the `actionList` parameter to the `addContributor` method. You may find [all available methods explained here](../../Common/Utils/Linked-List). +`ActionList` returns an instance of LinkedList class, a doubly linked list provided by the @abp/utils package. That instance is passed as the `actionList` parameter to the `addContributor` method. You may find [all available methods explained here](https://docs.abp.io/en/abp/latest/UI/Common/Utils/Linked-List). diff --git a/en/ui/aspnetcore/page-toolbar-extensions.md b/en/ui/aspnetcore/page-toolbar-extensions.md new file mode 100644 index 00000000..f4a648d6 --- /dev/null +++ b/en/ui/aspnetcore/page-toolbar-extensions.md @@ -0,0 +1,163 @@ +# Page Toolbar Extensions for ASP.NET Core UI + +Page toolbar system allows you to add components to the toolbar of any page. The page toolbar is the area right to the header of a page. A button ("Import users from excel") was added to the user management page below: + +![page-toolbar-button](../../images/page-toolbar-button.png) + +You can add any type of view component item to the page toolbar or modify existing items. + +## How to Set Up + +In this example, we will add an "Import users from excel" button and execute a JavaScript code for the user management page of the [Identity Module](../../modules/identity.md). + +### Add a New Button to the User Management Page + +Write the following code inside the `ConfigureServices` of your web module class: + +````csharp +Configure(options => +{ + options.Configure(toolbar => + { + toolbar.AddButton( + LocalizableString.Create("ImportFromExcel"), + icon: "file-import", + id: "ImportUsersFromExcel", + type: AbpButtonType.Secondary + ); + }); +}); +```` + +`AddButton` is a shortcut to simply add a button component. Note that you need to add the `ImportFromExcel` to your localization dictionary (json file) to localize the text. + +When you run the application, you will see the button added next to the current button list. There are some other parameters of the `AddButton` method (for example, use `order` to set the order of the button component relative to the other components). + +### Create a JavaScript File + +Now, we can go to the client side to handle click event of the new button. First, add a new JavaScript file to your solution. We added inside the `/Pages/Identity/Users` folder of the `.Web` project: + +![user-action-extension-on-solution](../../images/user-action-extension-on-solution.png) + +Here, the content of this JavaScript file: + +````js +$(function () { + $('#ImportUsersFromExcel').click(function (e) { + e.preventDefault(); + alert('TODO: import users from excel'); + }); +}); +```` + +In the `click` event, you can do anything you need to do. + +### Add the File to the User Management Page + +Then you need to add this JavaScript file to the user management page. You can take the power of the [Bundling & Minification system](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Bundling-Minification). + +Write the following code inside the `ConfigureServices` of your module class: + +````csharp +Configure(options => +{ + options.ScriptBundles.Configure( + typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel).FullName, + bundleConfiguration => + { + bundleConfiguration.AddFiles( + "/Pages/Identity/Users/my-user-extensions.js" + ); + }); +}); +```` + +This configuration adds `my-user-extensions.js` to the user management page of the Identity Module. `typeof(Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel).FullName` is the name of the bundle in the user management page. This is a common convention used for all the ABP Commercial modules. + +## Advanced Use Cases + +While you typically want to add a button action to the page toolbar, it is possible to add any type of component. + +### Add View Component to a Page Toolbar + +First, create a new view component in your project: + +![page-toolbar-custom-component](../../images/page-toolbar-custom-component.png) + +For this example, we've created a `MyToolbarItem` view component under the `/Pages/Identity/Users/MyToolbarItem` folder. + +`MyToolbarItemViewComponent.cs` content: + +````csharp +public class MyToolbarItemViewComponent : AbpViewComponent +{ + public IViewComponentResult Invoke() + { + return View("~/Pages/Identity/Users/MyToolbarItem/Default.cshtml"); + } +} +```` + +`Default.cshtml` content: + +````xml + + + +```` + +* `.cshtml` file can contain any type of component(s). It is a typical view component. +* `MyToolbarItemViewComponent` can inject and use any service if you need. + +Then you can add the `MyToolbarItemViewComponent` to the user management page: + +````csharp +Configure(options => +{ + options.Configure( + toolbar => + { + toolbar.AddComponent(); + } + ); +}); +```` + +* If your component accepts arguments (in the `Invoke`/`InvokeAsync` method), you can pass them to the `AddComponent` method as an anonymous object. + +#### Permissions + +If your button/component should be available based on a [permission/policy](https://docs.abp.io/en/abp/latest/Authorization), you can pass the permission/policy name as the `requiredPolicyName` parameter to the `AddButton` and `AddComponent` methods. + +### Add a Page Toolbar Contributor + +If you perform advanced custom logic while adding an item to a page toolbar, you can create a class that implements the `IPageToolbarContributor` interface or inherits from the `PageToolbarContributor` class: + +````csharp +public class MyToolbarContributor : PageToolbarContributor +{ + public override Task ContributeAsync(PageToolbarContributionContext context) + { + context.Items.Insert(0, new PageToolbarItem(typeof(MyToolbarItemViewComponent))); + + return Task.CompletedTask; + } +} +```` + +* You can use `context.ServiceProvider` to resolve dependencies if you need. + +Then add your class to the `Contributors` list: + +````csharp +Configure(options => +{ + options.Configure( + toolbar => + { + toolbar.Contributors.Add(new MyToolbarContributor()); + } + ); +}); +```` +