Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Practice/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ To learn M# properly, the best way is by doing. Every M# feature provides a shor

There are 22 practice projects that you should implement using M#. Each one should normally take less than an hours, and some maybe a bit longer. Each project is defined using a wireframe that explains what the software should do and how it should be structured. These wireframes are straight forward, but you should make sure to understand them fully before implementing.

To successfully build each practice project, you will be using and reusing some M# skills that you will have gained previously, plus one or two new skills and M# features. Links are provided for each one to the how-to documentation pages of specifically those new features.
To successfully build each practice project, you will be using and reusing some M# and Pangolin test cases skills that you will have gained previously, plus one or two new skills and M# features. Links are provided for each one to the how-to documentation pages of specifically those new features. Please use Pangolin to follow test driven development for each practice.

| Practice | To learn | Help |
|-------------|-----------|------|
Expand Down
16 changes: 8 additions & 8 deletions _sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,19 +147,19 @@

- [Search field on property](how-to/search/searchFieldOnProperty.md)
- [Full text search](how-to/search/fullTextSearch.md)
- Instant search
- [Instant search](how-to/search/instantSearch.md)
- [Default search value](how-to/search/defaultSearchValue.md)
- [Custom search element](how-to/search/customSearchElement.md)
- Auto-search on start

- How-to: View modules
- Custom data source
- [Custom data source](how-to/view-modules/customDataSource.md)
- Structured vs custom template
- Hiding empty elements

- How-to: Navigation
- Custom page url
- Auto page redirection
- [Custom page url](how-to/navigation/customPageUrl.md)
- [Auto page redirection](how-to/navigation/autoPageRedirection.md)
- [Modal (pop-up)](how-to/navigation/modal-PopUp.md)
- Ajax/no ajax
- Passing and using parameters
Expand All @@ -176,7 +176,7 @@
- Module: Shared vs page-owned
- [View components](how-to/uiComposition/viewComponents.md)
- [Master detail forms](how-to/uiComposition/masterDetailForms.md)
- Merged forms
- [Merged forms](how-to/uiComposition/mergedForms.md)
- Module references
- [Module boxes](how-to/uiComposition/moduleBoxes.md)

Expand All @@ -189,9 +189,9 @@

- Dependency Injection
- [PDF generation](how-to/misc/pdfGeneration.md)
- Page start-up logic
- [Page start-up logic](how-to/misc/pageStartUpLogic.md)
- Custom module initialization
- Custom methods in controller
- [Custom methods in controller](how-to/misc/customMethodsInController.md)
- [Custom ViewModel properties](how-to/misc/customViewModelProperties.md)
- [Scheduled tasks](how-to/misc/scheduledTasks.md)
- [Sending email notifications](how-to/misc/sendingEmailNotifications.md)
Expand All @@ -206,7 +206,7 @@

- Authentication
- [Role based access](how-to/security/roleBasedAccess.md)
- Page role inheritance
- [Page role inheritance](how-to/security/pageRoleInheritance.md)
- Custom page security
- Module specific security
- Module visibility vs security checks
Expand Down
57 changes: 57 additions & 0 deletions how-to/misc/customMethodsInController.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Custom Methods In Controller

## Problem

You want to handle logic for a page, using a method that is only available within the page's controller.

For example, an Accounts user has a list of Invoices to indicate whether or not they have already been paid. The user selects invoices that are not yet paid and then clicks the "Mark as Paid" button, which updates the invoices via a method in the controller and reloads the list.

## Implementation

This is possible via `OnControllerClassCode()`, which will create new code that gets generated for the controller.

Using the above example, a custom method can be created as follows:

```csharp
OnControllerClassCode("Pay selected invoices")
.Code(@"public async Task PayInvoices(vm.InvoicesList info)
{
var selected = await info.SelectedItems;

/*
* Rest of code
*/
}");
```

The parameter used for `OnControllerClassCode()` defines the comment that will go along with your code.

The `Code()` method is used alongside `OnControllerClassCode()` and handles the code that you want to add to your controller.

The above will generate the following in your controller:

```csharp
// Pay selected invoices
public async Task PayInvoices(vm.InvoicesList info)
{
var selected = await info.SelectedItems;

/*
* Rest of code
*/
}
```

> **Note:** When using the `Code()` method, because MSharp generates the code for the controller regardless of what you provided for the method in the UI project, any errors in the code would only get picked up in the Website project.

The new custom method can then be called within the module:

```csharp
Button("Mark as Paid")
.OnClick(x =>
{
x.CSharp("await PayInvoices(info);").ValidationError();
x.GentleMessage("Selected invoices paid.");
x.Reload();
});
```
29 changes: 29 additions & 0 deletions how-to/misc/pageStartUpLogic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Page Start-Up Logic

## Problem

You want to carry out specific logic, whenever a user navigates to the page.

A common example is when a user logs in and the application needs to redirect the user to the correct page, based on their given role.

## Implementation

With MSharp, you can achieve this by using `OnStart()` on the page itself.

```csharp
public class DispatchPage : SubPage<LoginPage>
{
public DispatchPage()
{
OnStart(x =>
{
x.If(AppRole.Admin).Go<AdminPage>().RunServerSide();
x.ElseIf(AppRole.Manager).Go<ManagerPage>().RunServerSide();

/*
* Additional code for handling redirecting the user based on their role
*/
});
}
}
```
23 changes: 23 additions & 0 deletions how-to/navigation/autoPageRedirection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Auto Page Redirection

## Problem

As soon as the user navigates to a page, you want that page to redirect the user to somewhere else.

An example would be when an Admin user wants to access the Settings section, which automatically redirects them to the Administrators page.

## Implementation

With MSharp, you can update the page to navigate to a different page, as part of `OnStart()`.

```csharp
public class SettingsPage : SubPage<AdminPage>
{
public SettingsPage()
{
Set(PageSettings.LeftMenu, "AdminSettingsMenu");

OnStart(x => x.Go<Settings.AdministratorsPage>().RunServerSide());
}
}
```
46 changes: 46 additions & 0 deletions how-to/navigation/customPageUrl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Custom Page Url

## Problem

By default, the url of a page is based on both the page hierarchy and the name of the page in question.

However, there may be a scenario where you want a page's url to be different from the default used.

For example, you have an application that has a page in Admin for handling general settings, with the following url:

> <http://examplesite.com/admin/settings/general>

However, you want the url for that page to be changed, whilst still retaining the name of the page itself.

## Implementation

With MSharp, you can set the `Route()` of the page to achieve this.

For example, the following will set the page's url to:

> <http://examplesite.com/admin/settings/general-settings>

```csharp
public class GeneralPage : SubPage<SettingsPage>
{
public GeneralPage()
{
Route("admin/settings/general-settings");
Add<Modules.GeneralSettingsForm>();
}
}
```

> **Note**: When setting the route, make sure that the custom url you want is not already being used by another page in the application.
>
> Also, depending where in the page hierarchy you are defining the custom url, what you provide for `Route()` can end up providing a different url than intended.
>
> Using the above example, if the `Route()` was instead set to just the following:
>
> ```csharp
> Route("general-settings");
> ```
>
> Then it would produce the following url:
> > <http://examplesite.com/general-settings>
>
23 changes: 23 additions & 0 deletions how-to/search/instantSearch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Instant Search

## Problem

The client would like to have a list refreshed as soon as a search filter gets updated.

For example, they want to search for all Employees within a specific Department and would like the list to be instantly updated, based on their selection.

## Implementation

With MSharp, adding `ReloadOnChange()` to the end of the search filter will cause the list to reload, whenever that filter's value gets updated.

```csharp
public EmployeesList()
{
HeaderText("Employees");

Search(x => x.Department).Control(ControlType.DropdownList).ReloadOnChange();

Column(x => x.Name);
Column(x => x.Department);
}
```
45 changes: 45 additions & 0 deletions how-to/security/pageRoleInheritance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Page Role Inheritance

## Problem

You want to limit who has access to sections of an application based on a given role, without having to handle the access on each individual page.

For example, only Admin users should have access to all pages that are part of the Settings section.

## Implementation

With MSharp, you can add Roles to the pages.

Whilst they can be applied on every page, you can instead apply them to just the root/parent pages in question.

Using the above example, you can simply apply the Roles for just the Settings page:

```csharp
public class SettingsPage : SubPage<HomePage>
{
public SettingsPage()
{
Roles(AppRole.Admin);

Set(PageSettings.LeftMenu, "AdminSettingsMenu");

OnStart(x => x.Go<Settings.GeneralPage>().RunServerSide());
}
}
```

This way, not only will the Settings page be restricted to Admin users, but it also means that all sub pages of Settings will by default be restricted to Admin users as well.

So even if no Roles were defined for the General page:

```csharp
public class GeneralPage : SubPage<SettingsPage>
{
public GeneralPage()
{
Add<Modules.GeneralSettingsForm>();
}
}
```

Because it is a sub page of Settings, it will by default still restrict the page to Admin users only.
43 changes: 43 additions & 0 deletions how-to/uiComposition/mergedForms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Merged Forms

## Problem

If Entity A has a one to one relationship with Entity B, you might want to be able to define the properties for Entity B in an Entity A form.

For example, each Contact has an Address property. Each Address has a Street Address, City and Post Code. You want to be able to define the Address properties for the associated Contact, within a Contact form.

## Implementation

You can achieve this by using Merged Forms.

This provides a sub form, the Merged Form, for Entity B, as part of the main form used for Entity A. In this case, the Contact form contains the Contact properties, whilst also providing the Address properties as part of the Merged Form.

```csharp
public ContactForm()
{
HeaderText("Contact Details");

Field(x => x.Name);
Field(x => x.Email);

MergedForm(x => x.Address, y =>
{
y.Field(x => x.StreetAddress);
y.Field(x => x.City);
y.Field(x => x.PostCode);
});

Button("Cancel")
.OnClick(x => x.ReturnToPreviousPage());

Button("Save").IsDefault().Icon(FA.Check)
.OnClick(x =>
{
x.SaveInDatabase();
x.GentleMessage("Saved successfully.");
x.ReturnToPreviousPage();
});
}
```

Using MergedForm(), the first parameter defines the property that the merged form is being used for, whilst the second parameter is used for defining the fields in the merged form.
32 changes: 32 additions & 0 deletions how-to/view-modules/customDataSource.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Custom Data Source

## Problem

When using a View module for an entity, the default data source is the ID of the entity that gets passed through from a previous page.

An example of this is when clicking on the Name of an Employee from a list, which then redirects you to view that Employee's details based on its ID.

In other cases, however, you might want to use a custom data source instead of the default used.

For example, you have a single View module that is used for showing any type of Content Block, such as Privacy Policy or Terms & Conditions. The module also uses custom markup and needs to show the correct Content Block, based on a provided Key.

## Implementation

With MSharp, you can change the data source used for a View module by using `DataSource()`.

```csharp
public class ContentBlockView : ViewModule<Domain.ContentBlock>
{
public ContentBlockView()
{
DataSource("await ContentBlock.FindByKey(info.Key)");

Markup("@info.Output.Raw()");

ViewModelProperty<string>("Key");
ViewModelProperty<string>("Output")
.Getter(@"if (Item == null) return ""No content found for key: '"" + Key + ""'"";
return Item.GetMarkup();");
}
}
```