Browse Source

Added page toolbar extensions document

pull/6944/head
Halil İbrahim Kalkan 5 years ago
parent
commit
a3f1dd1d19
  1. 7
      docs/en/Customizing-Application-Modules-Guide.md
  2. 420
      docs/en/UI/Angular/Page-Toolbar-Extensions.md
  3. BIN
      docs/en/UI/Angular/images/user-page-toolbar-extension-click-me-ng.png
  4. BIN
      docs/en/UI/Angular/images/user-page-toolbar-extension-custom-click-me-ng.png
  5. 163
      docs/en/UI/AspNetCore/Page-Toolbar-Extensions.md
  6. BIN
      docs/en/images/page-toolbar-button.png
  7. BIN
      docs/en/images/page-toolbar-custom-component.png

7
docs/en/Customizing-Application-Modules-Guide.md

@ -84,6 +84,13 @@ Data table column extension system allows you to add a new column in the data ta
* [Data Table Column Extensions for ASP.NET Core UI](UI/AspNetCore/Data-Table-Column-Extensions.md)
* [Data Table Column Extensions for Angular](UI/Angular/Data-Table-Column-Extensions.md)
#### Page Toolbar (TODO)
Page toolbar system allows you to add components to the toolbar of a page;
* [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)
#### Others
* [Dynamic Form Extensions for Angular](UI/Angular/Dynamic-Form-Extensions.md)

420
docs/en/UI/Angular/Page-Toolbar-Extensions.md

@ -0,0 +1,420 @@
# 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 '@abp/ng.theme.shared/extensions';
import { IdentityToolbarActionContributors, IdentityUserDto } from '@volo/abp.ng.identity';
const logUserNames = new ToolbarAction<IdentityUserDto[]>({
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<IdentityUserDto[]>
) {
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](../Common/Utils/Linked-List.md).
> **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 routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below:
```js
import { identityToolbarActionContributors } from './toolbar-action-contributors';
const routes: Routes = [
{
path: '',
component: DynamicLayoutComponent,
children: [
{
path: 'identity',
loadChildren: () =>
import('@volo/abp.ng.identity').then(m =>
m.IdentityModule.forLazy({
toolbarActionContributors: identityToolbarActionContributors,
}),
),
},
// other child routes
],
// other routes
}
];
```
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 '@abp/ng.theme.shared/extensions';
import { IdentityUserDto } from '@volo/abp.ng.identity';
@Component({
selector: 'app-click-me-button',
template: `
<button class="btn btn-warning" (click)="handleClick()">Click Me!</button>
`,
})
export class ClickMeButtonComponent {
constructor(
@Inject(EXTENSIONS_ACTION_DATA)
private data: ActionData<IdentityUserDto[]>
) {}
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. `IdentityUserDto[]`.
> We could also import `EXTENSIONS_ACTION_CALLBACK` from **@abp/ng.theme.shared/extensions** package, 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 '@abp/ng.theme.shared/extensions';
import { IdentityUserDto } from '@volo/abp.ng.identity';
import { IdentityToolbarActionContributors } from '@volo/abp.ng.identity/config';
import { ClickMeButtonComponent } from './click-me-button.component';
const logUserNames = new ToolbarComponent<IdentityUserDto[]>({
component: ClickMeButtonComponent,
// See ToolbarActionOptions in API section for all options
});
export function logUserNamesContributor(
actionList: ToolbarActionList<IdentityUserDto[]>
) {
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](../Common/Utils/Linked-List.md).
> **Important Note 1:** 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.
> **Important Note 2:** Please use one of the following if Ivy is not enabled in your project. Otherwise, you will get an "Expression form not supported." error.
```js
export const identityToolbarActionContributors: IdentityToolbarActionContributors = {
'Identity.UsersComponent': [ logUserNamesContributor ],
};
/* OR */
const identityContributors: IdentityToolbarActionContributors = {};
identityContributors[eIdentityComponents.Users] = [ logUserNamesContributor ];
export const identityToolbarActionContributors = identityContributors;
```
### Step 3. Import and Use Toolbar Action Contributors
Import `identityToolbarActionContributors` in your routing module and pass it to the static `forLazy` method of `IdentityModule` as seen below. If Ivy is not enabled in your project, do not forget putting `ClickMeButtonComponent` into `entryComponents`:
```js
import { identityToolbarActionContributors } from './toolbar-action-contributors';
const routes: Routes = [
{
path: '',
component: DynamicLayoutComponent,
children: [
{
path: 'identity',
loadChildren: () =>
import('@volo/abp.ng.identity').then(m =>
m.IdentityModule.forLazy({
toolbarActionContributors: identityToolbarActionContributors,
}),
),
},
// other child routes
],
// other routes
}
];
```
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)
## How to Place a Custom Modal and Trigger It by Toolbar Actions
Please check the same topic in [entity action extensions document](Entity-Action-Extensions.md) and replace entity action with a toolbar action.
## API
### ActionData\<R = any\>
`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\<T, R = any\>
`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<T, R = any> = (data?: ActionData<T>) => R;
```
### ActionPredicate\<T\>
`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<T> = (data?: ActionData<T>) => boolean;
```
### ToolbarActionOptions\<R = any\>
`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<R = any> = {
action: ActionCallback<R>,
text: string,
icon?: string,
permission?: string,
visible?: ActionPredicate<R>,
};
```
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\<R = any\>
`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<IdentityUserDto[]> = {
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\<R = any\>\(options: ToolbarActionOptions\<R\>\)** is used to create an instance of `ToolbarAction`.
```js
const action = ToolbarAction.create(options);
```
- **ToolbarAction.createMany\<R = any\>\(options: ToolbarActionOptions\<R\>\[\]\)** is used to create multiple instances of `ToolbarAction` with given array of `ToolbarActionOptions`.
### ToolbarComponentOptions\<R = any\>
`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<R = any> = {
component: Type<any>,
action?: ActionCallback<R>,
permission?: string,
visible?: ActionPredicate<R>,
};
```
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\<R = any\>
`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<IdentityUserDto[]> = {
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\<R = any\>\(options: ToolbarComponentOptions\<R\>\)** is used to create an instance of `ToolbarComponent`.
```js
const action = ToolbarComponent.create(options);
```
- **ToolbarComponent.createMany\<R = any\>\(options: ToolbarComponentOptions\<R\>\[\]\)** is used to create multiple instances of `ToolbarComponent` with given array of `ToolbarComponentOptions`.
```js
const actions = ToolbarComponent.createMany(optionsArray);
```
### ToolbarActionList\<R = any\>
`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](../Common/Utils/Linked-List.md).
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<IdentityUserDto[]>,
) {
// 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\<R = any\>
`ToolbarActionContributorCallback` is the type that you can pass as toolbar action contributor callbacks to static `forLazy` methods of the modules.
```js
// exportUsersContributor should have ToolbarActionContributorCallback<IdentityUserDto[]> type
export function exportUsersContributor(
actionList: ToolbarActionList<IdentityUserDto[]>,
) {
// add exportUsers just before the last action
actionList.add(exportUsers).byIndex(-1);
}
export const identityEntityActionContributors = {
[eIdentityComponents.Users]: [exportUsersContributor],
};
```
## See Also
- [Customizing Application Modules Guide](../../Customizing-Application-Modules-Guide.md)

BIN
docs/en/UI/Angular/images/user-page-toolbar-extension-click-me-ng.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
docs/en/UI/Angular/images/user-page-toolbar-extension-custom-click-me-ng.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

163
docs/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<AbpPageToolbarOptions>(options =>
{
options.Configure<Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel>(toolbar =>
{
toolbar.AddButton(
LocalizableString.Create<MyProjectNameResource>("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](Bundling-Minification.md).
Write the following code inside the `ConfigureServices` of your module class:
````csharp
Configure<AbpBundlingOptions>(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
<span>
<button type="button" class="btn btn-dark">CLICK ME</button>
</span>
````
* `.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<AbpPageToolbarOptions>(options =>
{
options.Configure<Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel>(
toolbar =>
{
toolbar.AddComponent<MyToolbarItemViewComponent>();
}
);
});
````
* 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](../../Authorization.md), 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<AbpPageToolbarOptions>(options =>
{
options.Configure<Volo.Abp.Identity.Web.Pages.Identity.Users.IndexModel>(
toolbar =>
{
toolbar.Contributors.Add(new MyToolbarContributor());
}
);
});
````

BIN
docs/en/images/page-toolbar-button.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/en/images/page-toolbar-custom-component.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Loading…
Cancel
Save