From 955782529fb3635b8aca96516c8c8fe61ccdb5d7 Mon Sep 17 00:00:00 2001 From: Shivraj Sillett Date: Mon, 19 Apr 2021 17:55:51 +0100 Subject: [PATCH 1/7] Update Practice readme.md text --- Practice/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Practice/readme.md b/Practice/readme.md index 2aa1e9f..10dc743 100644 --- a/Practice/readme.md +++ b/Practice/readme.md @@ -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 | |-------------|-----------|------| From 02587b0f675ad0c34a0b31d29e50941e8c278939 Mon Sep 17 00:00:00 2001 From: Shivraj Sillett Date: Tue, 20 Apr 2021 17:16:02 +0100 Subject: [PATCH 2/7] Add Instant Search page, update _sidebar.md --- _sidebar.md | 2 +- how-to/search/instantSearch.md | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 how-to/search/instantSearch.md diff --git a/_sidebar.md b/_sidebar.md index e46a7d3..c32f0dc 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -147,7 +147,7 @@ - [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 diff --git a/how-to/search/instantSearch.md b/how-to/search/instantSearch.md new file mode 100644 index 0000000..09c4860 --- /dev/null +++ b/how-to/search/instantSearch.md @@ -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); +} +``` From 88419243cd0f80c4ff82065e652b12406f567550 Mon Sep 17 00:00:00 2001 From: Shivraj Sillett Date: Tue, 20 Apr 2021 17:22:28 +0100 Subject: [PATCH 3/7] Add Merged Forms page, update _sidebar.md --- _sidebar.md | 2 +- how-to/uiComposition/mergedForms.md | 43 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 how-to/uiComposition/mergedForms.md diff --git a/_sidebar.md b/_sidebar.md index c32f0dc..ec8bddd 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -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) diff --git a/how-to/uiComposition/mergedForms.md b/how-to/uiComposition/mergedForms.md new file mode 100644 index 0000000..eca6622 --- /dev/null +++ b/how-to/uiComposition/mergedForms.md @@ -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. From 184c825a57878e166bfadee97c59b60b99a1d13f Mon Sep 17 00:00:00 2001 From: Shivraj Sillett Date: Wed, 21 Apr 2021 17:21:13 +0100 Subject: [PATCH 4/7] Add Navigation and View Module pages, update _sidebar.md --- _sidebar.md | 6 ++-- how-to/navigation/autoPageRedirection.md | 23 ++++++++++++ how-to/navigation/customPageUrl.md | 46 ++++++++++++++++++++++++ how-to/view-modules/customDataSource.md | 32 +++++++++++++++++ 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 how-to/navigation/autoPageRedirection.md create mode 100644 how-to/navigation/customPageUrl.md create mode 100644 how-to/view-modules/customDataSource.md diff --git a/_sidebar.md b/_sidebar.md index ec8bddd..058e5d9 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -153,13 +153,13 @@ - 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 diff --git a/how-to/navigation/autoPageRedirection.md b/how-to/navigation/autoPageRedirection.md new file mode 100644 index 0000000..f0d4f8c --- /dev/null +++ b/how-to/navigation/autoPageRedirection.md @@ -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 +{ + public SettingsPage() + { + Set(PageSettings.LeftMenu, "AdminSettingsMenu"); + + OnStart(x => x.Go().RunServerSide()); + } +} +``` diff --git a/how-to/navigation/customPageUrl.md b/how-to/navigation/customPageUrl.md new file mode 100644 index 0000000..b54f3f8 --- /dev/null +++ b/how-to/navigation/customPageUrl.md @@ -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: + +> + +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: + +> + +```csharp +public class GeneralPage : SubPage +{ + public GeneralPage() + { + Route("admin/settings/general-settings"); + Add(); + } +} +``` + +> **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: +> > +> diff --git a/how-to/view-modules/customDataSource.md b/how-to/view-modules/customDataSource.md new file mode 100644 index 0000000..acc4ea9 --- /dev/null +++ b/how-to/view-modules/customDataSource.md @@ -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 +{ + public ContentBlockView() + { + DataSource("await ContentBlock.FindByKey(info.Key)"); + + Markup("@info.Output.Raw()"); + + ViewModelProperty("Key"); + ViewModelProperty("Output") + .Getter(@"if (Item == null) return ""No content found for key: '"" + Key + ""'""; + return Item.GetMarkup();"); + } +} +``` From ad68d6967942dec55db65d0d793f0a91f0e7efec Mon Sep 17 00:00:00 2001 From: Shivraj Sillett Date: Thu, 22 Apr 2021 15:50:48 +0100 Subject: [PATCH 5/7] Add Page Start-Up Logic page, update _sidebar.md --- _sidebar.md | 2 +- how-to/misc/pageStartUpLogic.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 how-to/misc/pageStartUpLogic.md diff --git a/_sidebar.md b/_sidebar.md index 058e5d9..6ecf607 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -189,7 +189,7 @@ - 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 ViewModel properties](how-to/misc/customViewModelProperties.md) diff --git a/how-to/misc/pageStartUpLogic.md b/how-to/misc/pageStartUpLogic.md new file mode 100644 index 0000000..ac5dbbf --- /dev/null +++ b/how-to/misc/pageStartUpLogic.md @@ -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 +{ + public DispatchPage() + { + OnStart(x => + { + x.If(AppRole.Admin).Go().RunServerSide(); + x.ElseIf(AppRole.Manager).Go().RunServerSide(); + + /* + * Additional code for handling redirecting the user based on their role + */ + }); + } +} +``` From 99d0bfaa4eeba40150703a0bb059ccad9a9ccdfa Mon Sep 17 00:00:00 2001 From: Shivraj Sillett Date: Thu, 22 Apr 2021 17:18:25 +0100 Subject: [PATCH 6/7] Add Page Role Inheritance page, update _sidebar.md --- _sidebar.md | 2 +- how-to/security/pageRoleInheritance.md | 45 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 how-to/security/pageRoleInheritance.md diff --git a/_sidebar.md b/_sidebar.md index 6ecf607..658fdf2 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -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 diff --git a/how-to/security/pageRoleInheritance.md b/how-to/security/pageRoleInheritance.md new file mode 100644 index 0000000..e4bf5cb --- /dev/null +++ b/how-to/security/pageRoleInheritance.md @@ -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 +{ + public SettingsPage() + { + Roles(AppRole.Admin); + + Set(PageSettings.LeftMenu, "AdminSettingsMenu"); + + OnStart(x => x.Go().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 +{ + public GeneralPage() + { + Add(); + } +} +``` + +Because it is a sub page of Settings, it will by default still restrict the page to Admin users only. From 9a04844a0a970d7218d508736bbcbe596da3163f Mon Sep 17 00:00:00 2001 From: Shivraj Sillett Date: Fri, 23 Apr 2021 17:14:28 +0100 Subject: [PATCH 7/7] Add new Misc page, update _sidebar.md --- _sidebar.md | 2 +- how-to/misc/customMethodsInController.md | 57 ++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 how-to/misc/customMethodsInController.md diff --git a/_sidebar.md b/_sidebar.md index 658fdf2..113f9aa 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -191,7 +191,7 @@ - [PDF generation](how-to/misc/pdfGeneration.md) - [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) diff --git a/how-to/misc/customMethodsInController.md b/how-to/misc/customMethodsInController.md new file mode 100644 index 0000000..02885ea --- /dev/null +++ b/how-to/misc/customMethodsInController.md @@ -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(); + }); +```