From 2fad891a7791fbe6891a8b5bb8f09b81dbc32a32 Mon Sep 17 00:00:00 2001 From: christianbraeunlich Date: Sat, 5 Feb 2022 18:19:26 +0100 Subject: [PATCH 1/5] formatted nav patterns --- .../1-patterns/argument-table/index.md | 3 - .../1-patterns/blocked-entity/_index.md | 4 +- .../data-driven-blocked-entity/index.md | 11 +- .../cached-web-service-calls/index.md | 37 +-- .../1-patterns/copy-document/index.md | 6 +- .../create-data-from-templates/index.md | 51 ++-- .../create-urls-to-nav-clients/index.md | 275 ++++-------------- .../creating-custom-charts/index.md | 27 +- .../1-patterns/cross-session-events/index.md | 172 +++++------ .../currently-active-record/index.md | 23 +- .../1-patterns/data-migration-facade/index.md | 215 +++++++------- .../1-patterns/discovery-event/index.md | 14 +- .../NAVPatterns/1-patterns/document/index.md | 2 +- .../index.md | 9 +- .../error-message-processing/index.md | 25 +- .../index.md | 114 ++++---- content/NAVPatterns/1-patterns/hooks/index.md | 10 +- .../index.md | 43 +-- .../instructions-in-the-ui/index.md | 8 + .../journal-error-processing/index.md | 60 +--- .../journal-template-batch-line/index.md | 61 ++-- .../1-patterns/master-data/index.md | 115 +++----- .../1-patterns/multi-file-download/index.md | 43 +-- .../1-patterns/multi-page-list/index.md | 70 ++--- .../multilanguage-application-data/index.md | 32 +- .../net-exception-handling-in-cal/_index.md | 157 ++++++---- .../index.md | 182 +++++++----- .../NAVPatterns/1-patterns/no-series/index.md | 203 +++++-------- .../in-context-notifications/index.md | 6 +- .../index.md | 82 +++--- .../NAVPatterns/1-patterns/observer/index.md | 52 ++-- .../posting-routine-select-behavior/index.md | 8 +- .../select-distinct-with-queries/index.md | 13 +- .../index.md | 43 ++- .../index.md | 182 ++++++------ .../index.md | 20 +- .../1-patterns/released-entity/index.md | 25 +- .../1-patterns/report-selection/index.md | 64 ++-- .../1-sensitive-data-encapsulation/index.md | 71 +---- .../security/2-data-encryption/index.md | 139 +++------ .../3-single-point-of-access/index.md | 63 ++-- .../security/4-masked-text/index.md | 59 +--- .../1-patterns/security/5-ssl-in-nav/index.md | 55 +--- .../NAVPatterns/1-patterns/security/_index.md | 10 +- .../setup-specificity-fallback/index.md | 35 +-- .../silent-file-upload-and-download/index.md | 23 +- .../1-patterns/singleton/_index.md | 4 +- .../singleton/singleton-codeunit/index.md | 34 +-- .../singleton/singleton-table/_index.md | 47 +-- .../singleton-table/cue-table/index.md | 111 ++----- .../singleton-table/setup-table/index.md | 8 +- .../0250.Temporary-Dataset-Report-1.png | Bin 0 -> 46629 bytes .../2376.Temporary-Dataset-Report-2.png | Bin 0 -> 85238 bytes .../7607.Temporary-Dataset-Report-3.png | Bin 0 -> 5708 bytes .../temporary-dataset-report/index.md | 58 ++-- .../Fig-11.jpg | Bin 0 -> 54058 bytes .../Fig-12.jpg | Bin 0 -> 4847 bytes .../index.md | 10 +- .../1-patterns/variant-facade/index.md | 179 +++++------- 59 files changed, 1403 insertions(+), 1970 deletions(-) create mode 100644 content/NAVPatterns/1-patterns/temporary-dataset-report/0250.Temporary-Dataset-Report-1.png create mode 100644 content/NAVPatterns/1-patterns/temporary-dataset-report/2376.Temporary-Dataset-Report-2.png create mode 100644 content/NAVPatterns/1-patterns/temporary-dataset-report/7607.Temporary-Dataset-Report-3.png create mode 100644 content/NAVPatterns/1-patterns/totals-and-discounts-on-subpages-sales-and-purchases/Fig-11.jpg create mode 100644 content/NAVPatterns/1-patterns/totals-and-discounts-on-subpages-sales-and-purchases/Fig-12.jpg diff --git a/content/NAVPatterns/1-patterns/argument-table/index.md b/content/NAVPatterns/1-patterns/argument-table/index.md index 8bb49298..13a4c70d 100644 --- a/content/NAVPatterns/1-patterns/argument-table/index.md +++ b/content/NAVPatterns/1-patterns/argument-table/index.md @@ -77,10 +77,7 @@ New table ```AL TAB 50003 VAT Return Data PROCEDURE FillInVATReturnData@1200001(VAR VATReturnData@1200000 : Record 50003); -``` -**** -```AL VATReturnData.INIT; VATReturnData.NumberOfCopies := GetDefaultNumberOfCopies; VATReturnData.Uploaded := FALSE; diff --git a/content/NAVPatterns/1-patterns/blocked-entity/_index.md b/content/NAVPatterns/1-patterns/blocked-entity/_index.md index 1d3f2770..765791a5 100644 --- a/content/NAVPatterns/1-patterns/blocked-entity/_index.md +++ b/content/NAVPatterns/1-patterns/blocked-entity/_index.md @@ -93,10 +93,10 @@ The [Released Entity][anchor4]. {{< youtube O2R-fTSup1o >}} [anchor0]: 2260.BlockedEntityPattern.png -[anchor1]: /nav/w/designpatterns/247.data-driven-blocked-entity/edit +[anchor1]: /navpatterns/1-patterns/blocked-entity/data-driven-blocked-entity/ [anchor2]: 8637.BlockedEntityPattern_5F00_5F00_5F00_Boolean.png [anchor3]: 3056.BlockedEntityPattern_5F00_5F00_5F00_Option.png -[anchor4]: /nav/w/designpatterns/115.released-entity.aspx +[anchor4]: /navpatterns/1-patterns/released-entity/ [image0]: 2260.BlockedEntityPattern.png diff --git a/content/NAVPatterns/1-patterns/blocked-entity/data-driven-blocked-entity/index.md b/content/NAVPatterns/1-patterns/blocked-entity/data-driven-blocked-entity/index.md index 40da4b61..295cf07b 100644 --- a/content/NAVPatterns/1-patterns/blocked-entity/data-driven-blocked-entity/index.md +++ b/content/NAVPatterns/1-patterns/blocked-entity/data-driven-blocked-entity/index.md @@ -59,10 +59,11 @@ All the approval workflows include a response that restricts usage of a record, The code behind the "Add record restriction" workflow response: - RecRef.GETTABLE(Variant); - Workflow.GET(WorkflowStepInstance."Workflow Code"); - RecordRestrictionMgt.RestrictRecordUsage(RecRef.RECORDID,STRSUBSTNO(RestrictUsageDetailsTxt,Workflow.Code,Workflow.Description)); - +```AL +RecRef.GETTABLE(Variant); +Workflow.GET(WorkflowStepInstance."Workflow Code"); +RecordRestrictionMgt.RestrictRecordUsage(RecRef.RECORDID,STRSUBSTNO(RestrictUsageDetailsTxt,Workflow.Code,Workflow.Description)); +``` The code behind the "Remove record restriction" response: @@ -103,7 +104,7 @@ This pattern has been introduced in Dynamics NAV 2016\. [anchor0]: attention.jpg -[anchor1]: /nav/w/designpatterns/79.blocked-entity +[anchor1]: /navpatterns/1-patterns/blocked-entity/ [anchor2]: https://microsoft.sharepoint.com/teams/DynamicsNAV/Wiki/Nav%20Wiki%20Documents/NAV%20App%20Patterns/NAV%20App%20Patterns%20for%20Review/Data-Driven%20Blocked%20Entity.docx#_msocom_2 diff --git a/content/NAVPatterns/1-patterns/cached-web-service-calls/index.md b/content/NAVPatterns/1-patterns/cached-web-service-calls/index.md index a11e281e..baa4cea5 100644 --- a/content/NAVPatterns/1-patterns/cached-web-service-calls/index.md +++ b/content/NAVPatterns/1-patterns/cached-web-service-calls/index.md @@ -74,31 +74,34 @@ BEGIN END; LOCAL PROCEDURE IdentifyCountryRegionCode@1(VAR BankDataConvBank@1002 : Record 1259;Filter@1000 : Text) : Text; - VAR - CompanyInformation@1001 : Record 79; - BlankFilter@1003 : Text; - BEGIN - BlankFilter := ''''''; - IF Filter = BlankFilter THEN BEGIN - CompanyInformation.GET; - BankDataConvBank.SETFILTER("Country/Region Code",CompanyInformation."Country/Region Code"); - EXIT(BankDataConvBank.GETFILTER("Country/Region Code")); - END; - EXIT(Filter); +VAR + CompanyInformation@1001 : Record 79; + BlankFilter@1003 : Text; +BEGIN + BlankFilter := ''''''; + + IF Filter = BlankFilter THEN BEGIN + CompanyInformation.GET; + BankDataConvBank.SETFILTER("Country/Region Code",CompanyInformation."Country/Region Code"); + EXIT(BankDataConvBank.GETFILTER("Country/Region Code")); END; + EXIT(Filter); +END; + LOCAL PROCEDURE RefreshBankNamesOlderThanToday@5(CountryRegionCode@1000 : Text;ShowErrors@1002 : Boolean;Timeout@1004 : Integer); - VAR - BankDataConvBank@1001 : Record 1259; - ImpBankListExtDataHndl@1003 : Codeunit 1289; - BEGIN - IF CountryRegionCode <\> '' THEN - BankDataConvBank.SETFILTER("Country/Region Code",CountryRegionCode); +VAR + BankDataConvBank@1001 : Record 1259; + ImpBankListExtDataHndl@1003 : Codeunit 1289; +BEGIN + IF CountryRegionCode <> '' THEN + BankDataConvBank.SETFILTER("Country/Region Code",CountryRegionCode); BankDataConvBank.SETFILTER("Last Update Date",'<%1',TODAY); IF BankDataConvBank.FINDFIRST THEN ImpBankListExtDataHndl.GetBankListFromConversionService(ShowErrors,CountryRegionCode,Timeout); END; ``` + ## NAV Usages Bank name lookup on the Bank Account card for dynamically identifying the format to use to generate a bank-specific payment file. diff --git a/content/NAVPatterns/1-patterns/copy-document/index.md b/content/NAVPatterns/1-patterns/copy-document/index.md index 8086821d..324b617a 100644 --- a/content/NAVPatterns/1-patterns/copy-document/index.md +++ b/content/NAVPatterns/1-patterns/copy-document/index.md @@ -41,9 +41,9 @@ The Dynamics NAV application developer can take into account using the Copy Docu The Copy Document pattern involves the following entities: 1. Source document tables for document header and line. For example,Sales Header/Line. -2. Destination document tables for document header and line. +2. Destination document tables for document header and line. +**Note:** The source document header/line and destination document header/line tables do not need to be the same. For example, you can copy a Sales Shipment Header/Lines into a Sales Header/Lines. -**Note:**The source document header/line and destination document header/line tables do not need to be the same. For example, you can copy a Sales Shipment Header/Lines into a Sales Header/Lines. 3. Copy Document engine: COD6620, Copy Document Mgt. 4. Copy Document report for a specific document type. The report requires the following parameters: * Source Document Type @@ -77,7 +77,7 @@ Example: Copy Sales Document for Credit Memos. In the standard version of Microsoft Dynamics NAV, the Copy Document functionality is implemented in the Sales Credit Memo window as shown in the following section. -\*\*\* +**** **Precondition**: The user enters data in PAGE44, Sales Credit Memo. diff --git a/content/NAVPatterns/1-patterns/create-data-from-templates/index.md b/content/NAVPatterns/1-patterns/create-data-from-templates/index.md index f58dee98..d0d6366d 100644 --- a/content/NAVPatterns/1-patterns/create-data-from-templates/index.md +++ b/content/NAVPatterns/1-patterns/create-data-from-templates/index.md @@ -30,7 +30,7 @@ Using the patterns involves three steps. 1) As a first step, we must insert a record. This can be done either through C/AL code or by letting the user create a record using the **New** action. -2) After the record is created, we must apply the template. This is done by using the **UpdateRecord** function in the **Config. Template Management **codeunit (8612). +2) After the record is created, we must apply the template. This is done by using the **UpdateRecord** function in the **Config. Template Management** codeunit (8612). **Config. Template Lines** records reference one **Config. Template Header** record (lines pattern). The lines can be of type: @@ -39,7 +39,7 @@ Using the patterns involves three steps. The **UpdateRecord** function applies values to the record one line at the time. One of the requirements was to be possible to use configuration templates in different language/regional settings than the template was created in. -To support this scenario, when applying the**Config. Template Line** record, **GLOBALLANGUAGE** is set to the language ID of the field. This is important because the default value is stored as text, so we need to use the same formatting that NAV was running on when the template was created. Otherwise, data types, such as Boolean, Date, etc., will raise validation errors. +To support this scenario, when applying the **Config. Template Line** record, **GLOBALLANGUAGE** is set to the language ID of the field. This is important because the default value is stored as text, so we need to use the same formatting that NAV was running on when the template was created. Otherwise, data types, such as Boolean, Date, etc., will raise validation errors. Any updates to a **Config. Template Line** record will automatically update the language ID to the current one. Since lines are applied one by one, it is supported to have lines with different language IDs belonging to the same template. @@ -47,38 +47,39 @@ Any updates to a **Config. Template Line** record will automatically update the Code example (Insert a record, apply a template, and insert the related templates): -**// First insert a record Customer.INSERT(TRUE);** - -**// Apply a template RecRef.GETTABLE(Customer);** - ```al +// First insert a record Customer.INSERT(TRUE); + +// Apply a template RecRef.GETTABLE(Customer); + ConfigTemplateMgt.UpdateRecord(ConfigTemplateHeader,RecRef); RecRef.SETTABLE(Customer); -``` -**// Insert Dimensions -- related templates** -```al +// Insert Dimensions -- related templates + MiniDimensionsTemplate.InsertDimensionsFromTemplates(ConfigTemplateHeader,Customer."No.",DATABASE::Customer); ``` + Code to insert related templates (dimensions): + ```al FUNCTION InsertDimensionsFromTemplates(ConfigTemplateHeader : Record "Config. Template Header";MasterRecordNo : Code\[20\];TableID : Integer) + // There are multiple records (multiple dimensions per master record) // We have to set filter ConfigTemplateLine.SETRANGE(Type,ConfigTemplateLine.Type::"Related Template"); ConfigTemplateLine.SETRANGE("Data Template Code",ConfigTemplateHeader.Code IF ConfigTemplateLine.FINDSET THEN - REPEAT - ConfigTemplateHeader.GET(ConfigTemplateLine."Template Code"); - // Ensure that the table where the template belongs to is Dimensions - // We could have other related templates - IF ConfigTemplateHeader."Table ID" = DATABASE::"Default Dimension" THEN - InsertDimensionFromTemplate(ConfigTemplateHeader,MasterRecordNo,TableID); - UNTIL ConfigTemplateLine.NEXT = 0; -``` + REPEAT + ConfigTemplateHeader.GET(ConfigTemplateLine."Template Code"); -**// Create a new Dimensions Record and link it to the Master Record****** -```al + // Ensure that the table where the template belongs to is Dimensions + // We could have other related templates + IF ConfigTemplateHeader."Table ID" = DATABASE::"Default Dimension" THEN + InsertDimensionFromTemplate(ConfigTemplateHeader,MasterRecordNo,TableID); + UNTIL ConfigTemplateLine.NEXT = 0; + +// Create a new Dimensions Record and link it to the Master Record FUNCTION InsertDimensionFromTemplate(ConfigTemplateHeader : Record "Config. Template Header";MasterRecordNo : Code\[20\];TableID : Integer) DefaultDimension.INIT; DefaultDimension."No." := MasterRecordNo; @@ -100,7 +101,7 @@ FUNCTION InsertDimensionFromTemplate(ConfigTemplateHeader : Record "Config. Temp **To view or edit templates, you have two options:** -1. Use the **Config. Template List** table (8620) and the **Config. Template Header Card **table (8618). +1. Use the **Config. Template List** table (8620) and the **Config. Template Header Card** table (8618). This is a generic solution that is not very usable and is error-prone (no lookups, checks for length, table relation checks, etc.) The default value is a text field of 250 characters, which might be much more than the field length, and may lead to validation errors when used. Users will most likely not be able to use this page. @@ -121,9 +122,9 @@ The goals of this solution were: * To avoid any lateral effects of doing validation on the temporary master record. Doing validation on fields, even though the record itself is temporary, could permanently modify other data in the database. For example, if you insert a new record in the **Customer** table, even in a temporary table, a Contact record is created, which will not be temporary. * Testability: It is easy to test through RecordRef that the template table matches the main table. We can compare field lengths, data types, table relations, etc. The test is able to detect that they are out of sync, so it is easy to prevent errors. -One example in the product is the **Mini Customer Template **table (1300). +One example in the product is the **Mini Customer Template** table (1300). -The table itself contains very little code. OnModify, OnInsert, and OnDelete triggers update the **Configuration Header** and **Configuration Lines** tables. The following functions in the **Config. Template Management **codeunit (8612), are used for this: +The table itself contains very little code. OnModify, OnInsert, and OnDelete triggers update the **Configuration Header** and **Configuration Lines** tables. The following functions in the **Config. Template Management** codeunit (8612), are used for this: * ConfigTemplateManagement.CreateConfigTemplateAndLines * ConfigTemplateManagement.UpdateConfigTemplateAndLines @@ -142,15 +143,13 @@ In C5 2014, this is the workflow: The user opens the **Customers List** window and selects **New** -**[![ ][image3]][anchor3]** - -**[][anchor3]** +[![ ][image3]][anchor3] From this page, the user can view the template, edit it, or create a new one. Selecting a template will populate the customer card and open a new record. From the existing record, the user has options to save as a template or opening a list of templates to maintain available templates. Selecting a template will populate the customer card and open a new record. From the existing record, the user has options to save as a template or opening a list of templates to maintain available templates. [![ ][image4]][anchor4] -From the **Customer Card Template **window, we can invoke the **Dimensions** action, through which we can define the dimensions that will be inserted together with the template: +From the **Customer Card Template** window, we can invoke the **Dimensions** action, through which we can define the dimensions that will be inserted together with the template: [![ ][image5]][anchor5] diff --git a/content/NAVPatterns/1-patterns/create-urls-to-nav-clients/index.md b/content/NAVPatterns/1-patterns/create-urls-to-nav-clients/index.md index 00ef98d4..95a7373a 100644 --- a/content/NAVPatterns/1-patterns/create-urls-to-nav-clients/index.md +++ b/content/NAVPatterns/1-patterns/create-urls-to-nav-clients/index.md @@ -60,228 +60,65 @@ The GETURL function can generally be used every time a URL must be created. The The following are examples of calls to GETURL and their corresponding return value: -**Command** - -**URL** -```al -GETURL(CLIENTTYPE::Win) - -dynamicsnav://MyServer:7046/DynamicsNAV71// - -GETURL(CLIENTTYPE::Web) - -https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient - -GETURL(CLIENTTYPE::OData) - -http://MyServer:7048/DynamicsNAV71/OData - -GETURL(CLIENTTYPE::SOAP) - -http://MyServer:7047/DynamicsNAV71/WS/Services - -GETURL(CLIENTTYPE::Current) ie. When running this code on a Win client session - -dynamicsnav://MyServer:7046/DynamicsNAV71// - -GETURL(CLIENTTYPE::Default) ie. When the Server config key DefaultClient is set to Windows - -dynamicsnav://MyServer:7046/DynamicsNAV71// - -GETURL(CLIENTTYPE::Windows,COMPANYNAME) - -dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/ - -GETURL(CLIENTTYPE::Windows,'') - -dynamicsnav://MyServer:7046/DynamicsNAV71// - -GETURL(CLIENTTYPE::Windows,'NONEXISTING Corp') - -dynamicsnav://MyServer:7046/DynamicsNAV71/NONEXISTING Corp/ - -GETURL(CLIENTTYPE::Web,COMPANYNAME) - -https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=CRONUS - -GETURL(CLIENTTYPE::Web,'') - -https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient - -GETURL(CLIENTTYPE::Web,'NONEXISTING Corp') - -https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=NONEXISTING Corp - -GETURL(CLIENTTYPE::OData,COMPANYNAME) - -http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS') - -GETURL(CLIENTTYPE::OData,'') - -http://MyServer:7048/DynamicsNAV71/OData - -GETURL(CLIENTTYPE::OData,'NONEXISTING Corp') - -http://MyServer:7048/DynamicsNAV71/OData/Company('NONEXISTING Corp') - -GETURL(CLIENTTYPE::SOAP,COMPANYNAME) - -http://MyServer:7047/DynamicsNAV71/WS/CRONUS/Services - -GETURL(CLIENTTYPE::SOAP,'') - -http://MyServer:7047/DynamicsNAV71/WS/Services - -GETURL(CLIENTTYPE::SOAP,'NONEXISTING Corp') - -http://MyServer:7047/DynamicsNAV71/WS/NONEXISTING Corp/Services - -GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Table,27) - -dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runtable?table=27 - -GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Page,27) - -dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runpage?page=27 - -GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Report,6) - -dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runreport?report=6 - -GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Codeunit,5065) - -dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runcodeunit?codeunit=5065 - -GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Query,9150) - -dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runquery?query=9150 - -GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::XmlPort,5150) - -dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runxmlport?xmlport=5150 - -GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Page,27) ie. When the Web Service is published - -http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS')/PAG27Vendors - -GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Query,9150) ie. When the Web Service is published - -http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS')/QUE9150MyCustomers - -GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Page,27)Â ie. When the Web Service is published - -http://MyServer:7047/DynamicsNAV71/WS/CRONUS/Page/PAG27Vendors - -GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Codeunit,5065) ie. When the Web Service is published - -http://MyServer:7047/DynamicsNAV71/WS/CRONUS/Codeunit/COD5065EmailLogging - -GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Page,27,record) List Page - -dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runpage?page=27&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw - -GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Page,26,record) Card Page - -dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runpage?page=26&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw - -GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Page,27,record) List Page - -https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=CRONUS&page=27&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw - -GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Page,26,record) Card Page - -https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=CRONUS&page=26&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw - -GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Page,27,record) - -http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS')/PAG27Vendors('IC1030') - -GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Page,27) - -https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=CRONUS&page=27 - -GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Report,6) - -https://navwebsrvr:443/DynamicsNAV71\_Instance1/Webclient?company=CRONUS&report=6 - -``` +Command | URL +--------|------ +GETURL(CLIENTTYPE::Win) | dynamicsnav://MyServer:7046/DynamicsNAV71// +GETURL(CLIENTTYPE::Web) | https://navwebsrvr:443/DynamicsNAV71_Instance1/Webclient +GETURL(CLIENTTYPE::OData) | http://MyServer:7048/DynamicsNAV71/OData +GETURL(CLIENTTYPE::SOAP) | http://MyServer:7047/DynamicsNAV71/WS/Services +GETURL(CLIENTTYPE::Current) ie. When running this code on a Win client session | dynamicsnav://MyServer:7046/DynamicsNAV71// +GETURL(CLIENTTYPE::Default) ie. When the Server config key DefaultClient is set to Windows | dynamicsnav://MyServer:7046/DynamicsNAV71// +GETURL(CLIENTTYPE::Windows,COMPANYNAME) | dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/ +GETURL(CLIENTTYPE::Windows,'') | dynamicsnav://MyServer:7046/DynamicsNAV71// +GETURL(CLIENTTYPE::Windows,'NONEXISTING Corp') | dynamicsnav://MyServer:7046/DynamicsNAV71/NONEXISTING Corp/ +GETURL(CLIENTTYPE::Web,COMPANYNAME) | https://navwebsrvr:443/DynamicsNAV71_Instance1/Webclient?company=CRONUS +GETURL(CLIENTTYPE::Web,'') | https://navwebsrvr:443/DynamicsNAV71_Instance1/Webclient +GETURL(CLIENTTYPE::Web,'NONEXISTING Corp') | https://navwebsrvr:443/DynamicsNAV71_Instance1/Webclient?company=NONEXISTING Corp +GETURL(CLIENTTYPE::OData,COMPANYNAME) | http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS') +GETURL(CLIENTTYPE::OData,'') | http://MyServer:7048/DynamicsNAV71/OData +GETURL(CLIENTTYPE::OData,'NONEXISTING Corp') | http://MyServer:7048/DynamicsNAV71/OData/Company('NONEXISTING Corp') +GETURL(CLIENTTYPE::SOAP,COMPANYNAME) | http://MyServer:7047/DynamicsNAV71/WS/CRONUS/Services +GETURL(CLIENTTYPE::SOAP,'') | http://MyServer:7047/DynamicsNAV71/WS/Services +GETURL(CLIENTTYPE::SOAP,'NONEXISTING Corp') | http://MyServer:7047/DynamicsNAV71/WS/NONEXISTING Corp/Services +GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Table,27) | dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runtable?table=27 +GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Page,27) | dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runpage?page=27 +GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Report,6) | dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runreport?report=6 +GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Codeunit,5065) | dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runcodeunit?codeunit=5065 +GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Query,9150) | dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runquery?query=9150 +GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::XmlPort,5150) | dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runxmlport?xmlport=5150 +GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Page,27) ie. When the Web Service is published | http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS')/PAG27Vendors +GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Query,9150) ie. When the Web Service is published | http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS')/QUE9150MyCustomers +GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Page,27)Â ie. When the Web Service is published | http://MyServer:7047/DynamicsNAV71/WS/CRONUS/Page/PAG27Vendors +GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Codeunit,5065) ie. When the Web Service is published | http://MyServer:7047/DynamicsNAV71/WS/CRONUS/Codeunit/COD5065EmailLogging +GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Page,27,record) List Page | dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runpage?page=27&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw +GETURL(CLIENTTYPE::Windows,COMPANYNAME,OBJECTTYPE::Page,26,record) Card Page | dynamicsnav://MyServer:7046/DynamicsNAV71/CRONUS/runpage?page=26&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw +GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Page,27,record) List Page | https://navwebsrvr:443/DynamicsNAV71_Instance1/Webclient?company=CRONUS&page=27&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw +GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Page,26,record) Card Page | https://navwebsrvr:443/DynamicsNAV71_Instance1/Webclient?company=CRONUS&page=26&bookmark=23;FwAAAAJ7/0kAQwAxADAAMwAw +GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Page,27,record) | http://MyServer:7048/DynamicsNAV71/OData/Company('CRONUS')/PAG27Vendors('IC1030') +GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Page,27) | https://navwebsrvr:443/DynamicsNAV71_Instance1/Webclient?company=CRONUS&page=27 +GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Report,6) | https://navwebsrvr:443/DynamicsNAV71_Instance1/Webclient?company=CRONUS&report=6 If the GETURL function is called with invalid parameters, it will return an empty string. In that case, you can find the related error text by calling the GETLASTERRORTEXT function. -**Function Call** - -**Error Message** - -```al -GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Table,27) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Codeunit,5065) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Query,9150) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::XmlPort,5150) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Table,27) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Page,27) - -The Page object, 27, that is specified for the GetUrl function has not been published in the Web Services table. - -GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Report,6) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Codeunit,5065) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Query,9150) - -The Query object, 9150, that is specified for the GetUrl function has not been published in the Web Services table. - -GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::XmlPort,5150) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Table,27) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Page,27) - -The Page object, 27, that is specified for the GetUrl function has not been published in the Web Services table. - -GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Report,6) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Codeunit,5065) - -The Codeunit object, 5065, that is specified for the GetUrl function has not been published in the Web Services table. - -GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Query,9150) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::XmlPort,5150) - -The specified object type parameter for the GetUrl function is not valid. - -GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Page,27,record) - -``` - -You cannot specify a record parameter for the GetUrl function when the object type is SOAP +Function Call | Error Message +--------|------ +GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Table,27) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Codeunit,5065) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::Query,9150) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::Web,COMPANYNAME,OBJECTTYPE::XmlPort,5150) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Table,27) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Page,27) | The Page object, 27, that is specified for the GetUrl function has not been published in the Web Services table. +GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Report,6) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Codeunit,5065) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::Query,9150) | The Query object, 9150, that is specified for the GetUrl function has not been published in the Web Services table. +GETURL(CLIENTTYPE::OData,COMPANYNAME,OBJECTTYPE::XmlPort,5150) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Table,27) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Page,27) | The Page object, 27, that is specified for the GetUrl function has not been published in the Web Services table. +GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Report,6) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Codeunit,5065) | The Codeunit object, 5065, that is specified for the GetUrl function has not been published in the Web Services table. +GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Query,9150) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::XmlPort,5150) | The specified object type parameter for the GetUrl function is not valid. +GETURL(CLIENTTYPE::SOAP,COMPANYNAME,OBJECTTYPE::Page,27,record) | You cannot specify a record parameter for the GetUrl function when the object type is SOAP ## NAV Specific Example diff --git a/content/NAVPatterns/1-patterns/creating-custom-charts/index.md b/content/NAVPatterns/1-patterns/creating-custom-charts/index.md index f8a35c77..a8e1a9ae 100644 --- a/content/NAVPatterns/1-patterns/creating-custom-charts/index.md +++ b/content/NAVPatterns/1-patterns/creating-custom-charts/index.md @@ -38,7 +38,7 @@ This table is used to encapsulate the logic of the Business Chart Add-in. The ta * DrillDown logic * Other helper data related functions, for displaying date, periods, etc.\[Bogdana1\] \[NK2\] \[NK3\] -**Note:** It is recommended that you reuse the **Business Chart Buffer **table****(485) as a buffer table or extend. It is a generic table which should cover most of the use cases. Implement a new buffer table only if this table does not meet your needs. +**Note:** It is recommended that you reuse the **Business Chart Buffer table** (485) as a buffer table or extend. It is a generic table which should cover most of the use cases. Implement a new buffer table only if this table does not meet your needs. ### CardPart page @@ -67,10 +67,10 @@ One of the most common functionalities is personalization. If the chart can be c To do this, you need the following: * A setup record to store the data. You can use the **Business Chart User Setup** table (487) or create a new setup table if you need to store more information. -* A management codeunit to write/apply the settings to the chart and to encapsulate other logic. Since we should not write code on pages, the code for the actions and other logic that does not apply to the setup record****should go in this codeunit. +* A management codeunit to write/apply the settings to the chart and to encapsulate other logic. Since we should not write code on pages, the code for the actions and other logic that does not apply to the setup record should go in this codeunit. * Setup pages where users can customize how the chart is shown and set different settings. -The relation between the components is visualized in the following diagram\[ +The relation between the components is visualized in the following diagram: [![ ][image3]][anchor3] @@ -98,19 +98,22 @@ Implementation of the **Finance Performance Chart** page (762) ```al BusinessChart::AddInReady() - UpdateChart(Period::" "); + +UpdateChart(Period::" "); LOCAL UpdateChart(Period : ',Next,Previous') - MoveAndUpdateChart(Period,0); + +MoveAndUpdateChart(Period,0); LOCAL MoveAndUpdateChart(Period : ',Next,Previous';Move : Integer) - AccSchedChartManagement.GetSetupRecordset(AccountSchedulesChartSetup,AccountSchedulesChartSetup.Name,Move); - AccSchedChartManagement.UpdateData(Rec,Period,AccountSchedulesChartSetup); +AccSchedChartManagement.GetSetupRecordset(AccountSchedulesChartSetup,AccountSchedulesChartSetup.Name,Move); - Update(CurrPage.BusinessChart); +AccSchedChartManagement.UpdateData(Rec,Period,AccountSchedulesChartSetup); - StatusText := GetCurrentSelectionText("Period Filter Start Date","Period Filter End Date"); +Update(CurrPage.BusinessChart); + +StatusText := GetCurrentSelectionText("Period Filter Start Date","Period Filter End Date"); ``` In the MoveAndUpdateChart method, the AccSchedChartManagement codeunit gets a setup record and updates it if necessary. Then, it initializes the chart with setup data and sets the StatusText to show the period for which data is displayed. The same method is used by the actions to move and update the chart so that there is no code duplication. @@ -119,8 +122,10 @@ The following code is used to implement **DataPointClicked** ```al BusinessChart::DataPointClicked(point : DotNet "Microsoft.Dynamics.Nav.Client.BusinessChart.BusinessChartDataPoint") - SetDrillDownIndexes(point); - AccSchedChartManagement.DrillDown(Rec,AccountSchedulesChartSetup); + +SetDrillDownIndexes(point); + +AccSchedChartManagement.DrillDown(Rec,AccountSchedulesChartSetup); ``` SetDrillDownindexes is a method from the **Business Chart Buffer** table that maps the DotNet point variable to C/AL data, so it must be used. The next method that you must implement is the action to be performed on Drilldown. diff --git a/content/NAVPatterns/1-patterns/cross-session-events/index.md b/content/NAVPatterns/1-patterns/cross-session-events/index.md index 79f66ced..36b303b1 100644 --- a/content/NAVPatterns/1-patterns/cross-session-events/index.md +++ b/content/NAVPatterns/1-patterns/cross-session-events/index.md @@ -33,22 +33,22 @@ The pattern has four components: An example of this would be when we have multiple users looking at the same set of data and we want their screens to update in "real time" whenever one of them makes a change, without doing a full refresh. We will use the [Observer pattern][anchor2] to capture the change (act as the Publisher) and then create a Table to hold Subscribers and Filters (Change Observer), a Table to be the Message Queue (Change Notification), and a Codeunit to be the Message Broker and help with the polling (ObserverMgt). Below are the table definitions: -```al - //Change Observer: - //"Table ID" Integer "Observable Table" - "Server ID" Integer - "Session ID" Integer - - //Change Notification: - //"Table ID" Integer "Observable Table" - "Server ID" Integer - "Session ID" Integer - "Entry No." Integer AutoIncrement - "Type of Change" Option Insert,Modify,Delete,Rename - "Record ID" RecordID - ... (other fields to indicate what has changed) -``` + +**Change Observer:** | | +-----|------|----- +"Table ID" | Integer | "Observable Table" +"Server ID" | Integer +"Session ID" | Integer +**Change Notification:** | | +-----|------|----- +"Table ID" | Integer | "Observable Table" +"Server ID" | Integer +"Session ID" | Integer +"Entry No." | Integer | AutoIncrement +"Type of Change" | Option | Insert,Modify,Delete,Rename +"Record ID" | RecordID +... (other fields to indicate what has changed) The Change Observer table identifies the Subscriber using Server ID and Session ID, and then in this example there is only one filter, which is the Table ID we want to listen to any changes. In this case all three fields are in the Primary Key. @@ -58,85 +58,89 @@ _**Note:**_ Other examples of the pattern could have very different fields to id Our Message Broker Codeunit will also serve as a central place to create Subscribers (Listen and StopListening functions) and a place to Poll for Messages. Note that the Poll function deletes the Messages as it reads them. ```al - //Listen(TableID : Integer)** - WITH Observer DO BEGIN - "Table ID" := TableID; - "Server ID" := SERVICEINSTANCEID; - "Session ID" := SESSIONID; - INSERT(TRUE); - COMMIT; - END; +Listen(TableID : Integer) +WITH Observer DO BEGIN + "Table ID" := TableID; + "Server ID" := SERVICEINSTANCEID; + "Session ID" := SESSIONID; + INSERT(TRUE); + COMMIT; +END; - //StopListening(TableID : Integer) - WITH Observer DO BEGIN - RESET; - SETRANGE("Server ID",SERVICEINSTANCEID); - SETRANGE("Session ID",SESSIONID); - SETRANGE("Table ID",TableID); - DELETEALL(TRUE); - COMMIT; - END; +StopListening(TableID : Integer) +WITH Observer DO BEGIN + RESET; + SETRANGE("Server ID",SERVICEINSTANCEID); + SETRANGE("Session ID",SESSIONID); + SETRANGE("Table ID",TableID); + DELETEALL(TRUE); + COMMIT; +END; - //NotifyAll(ChangeNotification : Record "Change Notification")** - WITH Observer DO BEGIN - RESET; - SETRANGE("Table ID",ChangeNotification."Table ID"); - IF FINDSET THEN REPEAT - Notify(Observer,ChangeNotification); - UNTIL NEXT = 0; - END; +NotifyAll(ChangeNotification : Record "Change Notification") +WITH Observer DO BEGIN + RESET; + SETRANGE("Table ID",ChangeNotification."Table ID"); + IF FINDSET THEN REPEAT + Notify(Observer,ChangeNotification); + UNTIL NEXT = 0; +END; - //Notify(Observer : Record "Change Observer";ChangeNotification : Record "Change Notification")** - WITH ChangeNotification DO BEGIN - "Server ID" := Observer."Server ID"; - "Session ID" := Observer."Session ID"; - "Entry No." := 0; - INSERT; - END; +Notify(Observer : Record "Change Observer";ChangeNotification : Record "Change Notification") +WITH ChangeNotification DO BEGIN + "Server ID" := Observer."Server ID"; + "Session ID" := Observer."Session ID"; + "Entry No." := 0; + INSERT; +END; - //Poll(TableID : Integer;VAR TempChangeNotification : TEMPORARY Record "Change Notification")** - WITH ChangeNotification DO BEGIN - TempChangeNotification.RESET; - TempChangeNotification.DELETEALL; - RESET; - SETRANGE("Table ID",TableID); - SETRANGE("Server ID",SERVICEINSTANCEID); - SETRANGE("Session ID",SESSIONID); - IF FINDSET THEN REPEAT - TempChangeNotification := ChangeNotification; - TempChangeNotification.INSERT; - MARK(TRUE); - UNTIL NEXT = 0; - MARKEDONLY(TRUE); - DELETEALL; - END; +Poll(TableID : Integer;VAR TempChangeNotification : TEMPORARY Record "Change Notification") +WITH ChangeNotification DO BEGIN + TempChangeNotification.RESET; + TempChangeNotification.DELETEALL; + + RESET; + SETRANGE("Table ID",TableID); + SETRANGE("Server ID",SERVICEINSTANCEID); + SETRANGE("Session ID",SESSIONID); + + IF FINDSET THEN REPEAT + TempChangeNotification := ChangeNotification; + TempChangeNotification.INSERT; + MARK(TRUE); + UNTIL NEXT = 0; + + MARKEDONLY(TRUE); + DELETEALL; +END; ``` -**__** The final part of this example is an object that calls the functions above. In this example we will use a Page with a PingPong Timer Control to do the polling in (almost) real time. These are the functions on the page: +The final part of this example is an object that calls the functions above. In this example we will use a Page with a PingPong Timer Control to do the polling in (almost) real time. These are the functions on the page: ```al - //OnQueryClosePage(CloseAction : Action None) : Boolean** - ObserverMgt.StopListening(DATABASE::"NAV Whiteboard Booking"); - - //Timer::AddInReady()** - IF ObserverMgt.Listen(DATABASE::"NAV Whiteboard Booking") THEN - CurrPage.Timer.Ping(1000); - - //Timer::Pong()** - CallUpdate; - CurrPage.Timer.Ping(1000); - - //LOCAL CallUpdate()** - ObserverMgt.Poll(DATABASE::"NAV Whiteboard Booking",TempChangeNotification); - WITH TempChangeNotification DO BEGIN - IF FINDSET THEN REPEAT +OnQueryClosePage(CloseAction : Action None) : Boolean +ObserverMgt.StopListening(DATABASE::"NAV Whiteboard Booking"); + +Timer::AddInReady() +IF ObserverMgt.Listen(DATABASE::"NAV Whiteboard Booking") THEN +CurrPage.Timer.Ping(1000); + +Timer::Pong() +CallUpdate; +CurrPage.Timer.Ping(1000); + +LOCAL CallUpdate() +ObserverMgt.Poll(DATABASE::"NAV Whiteboard Booking",TempChangeNotification); + +WITH TempChangeNotification DO BEGIN + IF FINDSET THEN REPEAT IF "Type of Change" = "Type of Change"::Delete THEN BEGIN - ... + ... END ELSE IF RecRef.GET("Record ID") THEN BEGIN - ... + ... END; - UNTIL NEXT = 0; - END; + UNTIL NEXT = 0; +END; ``` ## Consequences @@ -157,7 +161,7 @@ Below is the Wikipedia link to the PubSub pattern [anchor0]: PubSub.png [anchor1]: https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern -[anchor2]: /nav/w/designpatterns/248.observer +[anchor2]: /navpatterns/1-patterns/observer/ [anchor3]: https://geeknikolai.wordpress.com/2015/10/30/pubsub-pattern-in-dynamics-nav-2016/ diff --git a/content/NAVPatterns/1-patterns/currently-active-record/index.md b/content/NAVPatterns/1-patterns/currently-active-record/index.md index c3913c8f..1f1f56a0 100644 --- a/content/NAVPatterns/1-patterns/currently-active-record/index.md +++ b/content/NAVPatterns/1-patterns/currently-active-record/index.md @@ -20,7 +20,7 @@ There is no way in NAV to get a set of records from the database, which all have * The NAV Service Tier receives and throws away data. -### Ending Date Problem +## Ending Date Problem Ending Date may introduce some problems of its own. @@ -46,15 +46,15 @@ In the following example, we have a fictive table containing: Code, Starting Dat You will need to create the view before you define the Table Object. You will need to create a view for every company in the database. ```sql - CREATE VIEW [dbo].[CRONUS$PriceView] - AS - SELECT [Code], [Starting Date], [Price] - FROM dbo.[CRONUS$Price] AS A - WHERE [Starting Date] = - (SELECT MAX([Starting Date]) - FROM dbo.[CRONUS$Price] AS B - WHERE B.[Code] = A.[Code] AND - B.[Starting Date] <= GETDATE()) +CREATE VIEW [dbo].[CRONUS$PriceView] +AS +SELECT [Code], [Starting Date], [Price] +FROM dbo.[CRONUS$Price] AS A +WHERE [Starting Date] = +(SELECT MAX([Starting Date]) +FROM dbo.[CRONUS$Price] AS B +WHERE B.[Code] = A.[Code] AND +B.[Starting Date] <= GETDATE()) ``` Test the view to ensure that you get the correct result. It is much easier to test now than later. @@ -66,7 +66,7 @@ Remember to set the link table property before you save it. ### 3. Implement the code ```al - IF PriceView.FINDSET THEN // You have them +IF PriceView.FINDSET THEN // You have them ``` ### 4. Create a deployment codeunit @@ -134,7 +134,6 @@ The pattern does not exist in NAV (yet J). We have used it several times in our ## Ideas for improvement - Query Object should be able to handle sub-selects and Unions. A simple solution could be to allow the NAV developer to specify the actual Select statement inside the query Object in clear text. Opening up for writing your own queries and map the projection to the Query-defined fields will make the query Object very versatile and remove the pressure from Microsoft trying to create all the different permutations that a select statement can have. Microsoft and others have all tried to create wizards that can create SQL select statement. They all end up having a clear text option. An alternative would be better support for linked table objects, specifically views. The current implementation is very fragile. diff --git a/content/NAVPatterns/1-patterns/data-migration-facade/index.md b/content/NAVPatterns/1-patterns/data-migration-facade/index.md index f92b60d7..cd90d96d 100644 --- a/content/NAVPatterns/1-patterns/data-migration-facade/index.md +++ b/content/NAVPatterns/1-patterns/data-migration-facade/index.md @@ -12,7 +12,7 @@ This pattern is describing how you can migrate data using the Data Migration Fa ## Problem: -Writing migration code from an external source, such as a product from a competitor, can be time consuming, as you need to tackle the problems of what to migrate, in which order, exception handling... and can result in code that is fragile due to deep dependencies on the NAV data model (high coupling). Any change to the NAV data model can easily break this code. **** +Writing migration code from an external source, such as a product from a competitor, can be time consuming, as you need to tackle the problems of what to migrate, in which order, exception handling... and can result in code that is fragile due to deep dependencies on the NAV data model (high coupling). Any change to the NAV data model can easily break this code. ## Solution: @@ -35,7 +35,7 @@ The façade framework has the following components: * A page where you can view the status and progress of the migration. It shows one line for each master data entity (item, customer, vendor, general ledger account) that was chosen for migration. You can also stop a migration by choosing the **Stop Migration** action. -* * **Data Migration Overview** (page 1799) + * **Data Migration Overview** (page 1799) ## Usage: @@ -47,10 +47,10 @@ There are the following use cases: To initialize and start the data migration, you must call the following procedures: -* **"Data Migration Status Façade".InitStatusLine('My Migration Type',Database::Item,42000,0,Codeunit::"My extension Item migration codeunit") ** +* **"Data Migration Status Façade".InitStatusLine('My Migration Type',Database::Item,42000,0,Codeunit::"My extension Item migration codeunit")** This deletes existing status lines for migrating Items for 'My Migration Type', and initializes a new status line with 0 records migrated out of 42000\. -**"Data Migration Façade".StartMigration('My Migration Type',false) ** +**"Data Migration Façade".StartMigration('My Migration Type',false)** This starts the migration. False means this is not a retry. A re-try is when you migrate one or more records from the **Show Errors** page, which is described later in this document. Retry = true is only used by the **Show Errors** page and should not be used from extensions. ### Usage without staging tables: @@ -70,56 +70,61 @@ The overall workflow is: [![ ][image1]][anchor1] -_Figure 1: sequence diagram of the data migration without staging tables _ +_Figure 1: sequence diagram of the data migration without staging tables_ The following example shows how to migrate items without staging tables: ```al - trigger OnRun(); - var - ItemDataMigrationFacade: Codeunit "Item Data Migration Facade"; - ItemNumber: Integer; - ItemJson: Text; - begin - // loop on items retrieved through a web service for example - for ItemNumber := 1 to ExternalWebService.GetItemCount do begin - ExternalWebService.GetItem(ItemNumber,ItemJson); - // create item using the facade - if not ItemDataMigrationFacade.CreateItemIfNeeded(ItemJson.ItemNumber,ItemJson.ItemName1, - ItemJson.ItemName2,ConvertItemType(ItemJson.ItemType)) then - exit; // item already exists - // set some fields using the facade - ItemDataMigrationFacade.SetVendorItemNo(ItemJson.VendItemNumber); - ItemDataMigrationFacade.SetUnitVolume(ItemJson.Volume); - ItemDataMigrationFacade.SetAlternativeItemNo(ItemJson.AltItemNumber); - if ItemJson.PrimaryVendor <\> '' then - ItemDataMigrationFacade.SetVendorNo(ItemJson.PrimaryVendor); - // migrate dependencies - MigrateItemUnitOfMeasure(ItemDataMigrationFacade,ItemJson); - // modify the item (+run trigger) to save the changes made by setters - ItemDataMigrationFacade.ModifyItem(true); - // update the status in the migration dashboard - DataMigrationStatusFacade.IncrementMigratedRecordCount('My Migration Type', - Database::Item,1); - end; - end; - procedure MigrateItemUnitOfMeasure(ItemDataMigrationFacade : Codeunit "Item Data Migration Facade"; - ItemJson : Text); - var - MyUnitCodeStagingTable: Record "My Unit Code Staging Table"; - DataMigrationStatusFacade: Codeunit "Data Migration Status Facade"; - DescriptionToSet: Text\[10\]; - UnitCodeJson: Text; - begin - if ItemJson.UnitCode = '' then - // log an error using the Data migration façade - DataMigrationStatusFacade.RegisterErrorNoStagingTablesCase( - 'My Migration Type',Database::Item,'Unit of measure is empty.'); - if ExternalWebService.GetUnitCode(ItemJson.UnitCode,UnitCodeJson) then - DescriptionToSet := UnitCodeJson.Description; - ItemDataMigrationFacade.CreateUnitOfMeasureIfNeeded(ItemJson.UnitCode, DescriptionToSet); - // set the unit of measure on the item - ItemDataMigrationFacade.SetBaseUnitOfMeasure(ItemJson.UnitCode); - end; +trigger OnRun(); +var + ItemDataMigrationFacade: Codeunit "Item Data Migration Facade"; + ItemNumber: Integer; + ItemJson: Text; +begin + // loop on items retrieved through a web service for example + for ItemNumber := 1 to ExternalWebService.GetItemCount do begin + ExternalWebService.GetItem(ItemNumber,ItemJson); + + // create item using the facade + if not ItemDataMigrationFacade.CreateItemIfNeeded(ItemJson.ItemNumber,ItemJson.ItemName1, + ItemJson.ItemName2,ConvertItemType(ItemJson.ItemType)) then + exit; // item already exists + + // set some fields using the facade + ItemDataMigrationFacade.SetVendorItemNo(ItemJson.VendItemNumber); + ItemDataMigrationFacade.SetUnitVolume(ItemJson.Volume); + ItemDataMigrationFacade.SetAlternativeItemNo(ItemJson.AltItemNumber); + if ItemJson.PrimaryVendor <> '' then + ItemDataMigrationFacade.SetVendorNo(ItemJson.PrimaryVendor); + + // migrate dependencies + MigrateItemUnitOfMeasure(ItemDataMigrationFacade,ItemJson); + + // modify the item (+run trigger) to save the changes made by setters + ItemDataMigrationFacade.ModifyItem(true); + // update the status in the migration dashboard + DataMigrationStatusFacade.IncrementMigratedRecordCount('My Migration Type',Database::Item,1); + end; +end; + +procedure MigrateItemUnitOfMeasure(ItemDataMigrationFacade : Codeunit "Item Data Migration Facade"; ItemJson : Text); +var + MyUnitCodeStagingTable: Record "My Unit Code Staging Table"; + DataMigrationStatusFacade: Codeunit "Data Migration Status Facade"; + DescriptionToSet: Text\[10\]; + UnitCodeJson: Text; +begin + if ItemJson.UnitCode = '' then + // log an error using the Data migration façade + DataMigrationStatusFacade.RegisterErrorNoStagingTablesCase( + 'My Migration Type',Database::Item,'Unit of measure is empty.'); + + if ExternalWebService.GetUnitCode(ItemJson.UnitCode,UnitCodeJson) then + DescriptionToSet := UnitCodeJson.Description; + ItemDataMigrationFacade.CreateUnitOfMeasureIfNeeded(ItemJson.UnitCode, DescriptionToSet); + + // set the unit of measure on the item + ItemDataMigrationFacade.SetBaseUnitOfMeasure(ItemJson.UnitCode); +end; ``` _Figure 2: Example of Item and Item Unit of Measure migration without staging tables_ @@ -136,7 +141,7 @@ The overall workflow is: * Subscribe to the events to migrate entities and their dependencies. * From the event subscribers, call the façade procedures to create entities and set their field values: -* * **"Item Data Migration Façade".CreateItemIfNeeded('ITEM042','My Item Description';'My Item Description 2';ItemTypeToSet::Inventory)** + * **"Item Data Migration Façade".CreateItemIfNeeded('ITEM042','My Item Description';'My Item Description 2';ItemTypeToSet::Inventory)** * **"Item Data Migration Façade".SetBaseUnitOfMeasure('BOX')** * **"Item Data Migration Façade".ModifyItem(true)** @@ -146,66 +151,72 @@ _Figure 3: Simplified sequence diagram of the data migration with staging tables Below is a simplified example showing how to create an item: ```al - [EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Data Migration Facade", 'OnMigrateItem', '', true, true)] - procedure OnMigrateItem(VAR Sender : Codeunit "Item Data Migration Facade";RecordIdToMigrate : RecordId); - var - MyItemStagingTable : Record "My Item Staging Table"; - begin - // handle the event if it targets this extension's staging table - if RecordIdToMigrate.TableNo <\> Database::"My Item Staging Table" then - exit; - MyItemStagingTable.Get(RecordIdToMigrate); - // create item using the facade - if not Sender.CreateItemIfNeeded(MyItemStagingTable.ItemNumber,MyItemStagingTable.ItemName1, - MyItemStagingTable.ItemName2,ConvertItemType(MyItemStagingTable.ItemType)) then - exit; // item already exists - // set some fields using the facade - Sender.SetVendorItemNo(MyItemStagingTable.VendItemNumber); - Sender.SetUnitVolume(MyItemStagingTable.Volume); - Sender.SetAlternativeItemNo(MyItemStagingTable.AltItemNumber); - if MyItemStagingTable.PrimaryVendor <\> '' then - Sender.SetVendorNo(MyItemStagingTable.PrimaryVendor); - // modify the item (+run trigger) to save the changes made by setters - Sender.ModifyItem(true); - end; +[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Data Migration Facade", 'OnMigrateItem', '', true, true)] +procedure OnMigrateItem(VAR Sender : Codeunit "Item Data Migration Facade";RecordIdToMigrate : RecordId); +var + MyItemStagingTable : Record "My Item Staging Table"; +begin + // handle the event if it targets this extension's staging table + if RecordIdToMigrate.TableNo <> Database::"My Item Staging Table" then + exit; + MyItemStagingTable.Get(RecordIdToMigrate); + + // create item using the facade + if not Sender.CreateItemIfNeeded(MyItemStagingTable.ItemNumber,MyItemStagingTable.ItemName1, + MyItemStagingTable.ItemName2,ConvertItemType(MyItemStagingTable.ItemType)) then + exit; // item already exists + + // set some fields using the facade + Sender.SetVendorItemNo(MyItemStagingTable.VendItemNumber); + Sender.SetUnitVolume(MyItemStagingTable.Volume); + Sender.SetAlternativeItemNo(MyItemStagingTable.AltItemNumber); + if MyItemStagingTable.PrimaryVendor <> '' then + Sender.SetVendorNo(MyItemStagingTable.PrimaryVendor); + + // modify the item (+run trigger) to save the changes made by setters + Sender.ModifyItem(true); +end; ``` _Figure 4: Example of event subscriber for Item migration_ - Below is another example showing how to use additional events to set fields that reference other tables, here the unit of measure: ```al - [EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Data Migration Facade", 'OnMigrateItemUnitOfMeasure', '', true, true)] - procedure OnMigrateItemUnitOfMeasure(VAR Sender : Codeunit "Item Data Migration Facade";RecordIdToMigrate : RecordId); - var - MyItemStagingTable : Record "My Item Staging Table"; - MyUnitCodeStagingTable : Record "My Unit Code Staging Table"; - DescriptionToSet: Text[10]; - begin - // handle the event if it targets this extension's staging table - if RecordIdToMigrate.TableNo <\> Database::"My Item Staging Table" then - exit; - MyItemStagingTable.Get(RecordIdToMigrate); - if MyItemStagingTable.UnitCode = '' then - error('Unit of measure is empty.'); - MyUnitCodeStagingTable.SetRange(UnitCode,MyItemStagingTable.UnitCode); - if MyUnitCodeStagingTable.FindFirst then - DescriptionToSet := MyUnitCodeStagingTable.Description; - // create the unit of measure through the facade - Sender.CreateUnitOfMeasureIfNeeded(MyItemStagingTable.UnitCode, DescriptionToSet); - // set the unit of measure on the item - Sender.SetBaseUnitOfMeasure(MyItemStagingTable.UnitCode); - // modify the item to save the changes made by setter - Sender.ModifyItem(false); - end; +[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Data Migration Facade", 'OnMigrateItemUnitOfMeasure', '', true, true)] +procedure OnMigrateItemUnitOfMeasure(VAR Sender : Codeunit "Item Data Migration Facade";RecordIdToMigrate : RecordId); +var + MyItemStagingTable : Record "My Item Staging Table"; + MyUnitCodeStagingTable : Record "My Unit Code Staging Table"; + DescriptionToSet: Text[10]; +begin + // handle the event if it targets this extension's staging table + if RecordIdToMigrate.TableNo <\> Database::"My Item Staging Table" then + exit; + + MyItemStagingTable.Get(RecordIdToMigrate); + if MyItemStagingTable.UnitCode = '' then + error('Unit of measure is empty.'); + MyUnitCodeStagingTable.SetRange(UnitCode,MyItemStagingTable.UnitCode); + if MyUnitCodeStagingTable.FindFirst then + DescriptionToSet := MyUnitCodeStagingTable.Description; + + // create the unit of measure through the facade + Sender.CreateUnitOfMeasureIfNeeded(MyItemStagingTable.UnitCode, DescriptionToSet); + + // set the unit of measure on the item + Sender.SetBaseUnitOfMeasure(MyItemStagingTable.UnitCode); + + // modify the item to save the changes made by setter + Sender.ModifyItem(false); +end; ``` _Figure 5: Example of event subscriber for Item Unit of Measure migration_ ### Combining both approaches: -If you want to migrate additional entities, the **Data Migration **framework lets you initialize the migration with entities other than master data. In this case, the **Data Migration Overview** page will show additional lines. Item, vendor, customer, an general ledger accounts are migrated with an event driven approach, and the additional entities are migrated by calling an extension codeunit **OnRun** method. +If you want to migrate additional entities, the **Data Migration** framework lets you initialize the migration with entities other than master data. In this case, the **Data Migration Overview** page will show additional lines. Item, vendor, customer, an general ledger accounts are migrated with an event driven approach, and the additional entities are migrated by calling an extension codeunit **OnRun** method. ## Error handling with staging tables: @@ -215,17 +226,13 @@ The migration starts by calling **RUN** on the façade codeunit. Errors thrown d _Figure 6: List of errors shown when clicking **Show Errors** on the **Data Migration Overview** page_ -__ - The **Edit Record** action opens a view of the staging table, where you can edit fields to fix errors. Figure 4 shows an example of a page for the vendor staging table in a migration from C5\. [![ ][image4]][anchor4] _Figure 7: Edit a staging table record_ -__ - -The **Staging Table ID** determines the page to open, so it is important that the page ID is equal to the **Staging Table ID**, at least for the master data staging tables, for example, for**G/L Accounts, Items, Customers** and **Vendor**. You should ensure that pages to edit related entities are linked on this page by means of new actions. For example, Figure 4 uses the **C5 Purchaser** action. +The **Staging Table ID** determines the page to open, so it is important that the page ID is equal to the **Staging Table ID**, at least for the master data staging tables, for example, for **G/L Accounts, Items, Customers** and **Vendor**. You should ensure that pages to edit related entities are linked on this page by means of new actions. For example, Figure 4 uses the **C5 Purchaser** action. After you fix the staging table record, you can choose the **Migrate** action to mark the selected records as records to retry, and then run **StartMigration** with the **Retry** flag set to true. This is the only place where the retry flag should be set to true in the **StartMigration** procedure. diff --git a/content/NAVPatterns/1-patterns/discovery-event/index.md b/content/NAVPatterns/1-patterns/discovery-event/index.md index 3c18b8f2..eba8381a 100644 --- a/content/NAVPatterns/1-patterns/discovery-event/index.md +++ b/content/NAVPatterns/1-patterns/discovery-event/index.md @@ -18,13 +18,15 @@ The pattern is most easily described when you look at an example. This example i [![ ][image0]][anchor0] -The goal of this functionality is to: -• List all the different connections to external services, -• Have a central place to navigate to the corresponding setup of the service. +The goal of this functionality is to: + +* List all the different connections to external services, +* Have a central place to navigate to the corresponding setup of the service. -The functionality (**Service Connections**) itself, is not aware of the state nor setup nor any context of all the different services in the list. All it does is: -• It raises an event as an opportunity for all services within the NAV application to subscribe to, -• It has a public function **InsertServiceConnection** that the subscribers can use to register itself at the Service Connection. +The functionality (**Service Connections**) itself, is not aware of the state nor setup nor any context of all the different services in the list. All it does is: + +* It raises an event as an opportunity for all services within the NAV application to subscribe to, +* It has a public function **InsertServiceConnection** that the subscribers can use to register itself at the Service Connection. The event **OnRegisterServiceConnection** is raised when the page (1279 - **Service Connections**) is opened. diff --git a/content/NAVPatterns/1-patterns/document/index.md b/content/NAVPatterns/1-patterns/document/index.md index 6c10f91c..653ed482 100644 --- a/content/NAVPatterns/1-patterns/document/index.md +++ b/content/NAVPatterns/1-patterns/document/index.md @@ -61,7 +61,7 @@ Create the page for "Document Header" Table with the wizard by selecting the Doc Then simply add your subpage as new line in the designer, and adjust the property "SubFormPerLink" with "Document No.=FIELD(No.)" to link header and lines : -**[![ ][image1]][anchor1]** +[![ ][image1]][anchor1] **Page "Document List":** Use the Page wizard to create a List page based on the Document table and add fields, FactBox (RecordLinks, Notes...), etc. diff --git a/content/NAVPatterns/1-patterns/easy-update-of-setup-or-supplementary-information/index.md b/content/NAVPatterns/1-patterns/easy-update-of-setup-or-supplementary-information/index.md index 24eeb7c8..d1e704e7 100644 --- a/content/NAVPatterns/1-patterns/easy-update-of-setup-or-supplementary-information/index.md +++ b/content/NAVPatterns/1-patterns/easy-update-of-setup-or-supplementary-information/index.md @@ -17,7 +17,7 @@ To guide users, we can prompt them with a dialog on which they can update the se Define two functions in the setup or supplementary table: One for verifying if the needed information is available, and another for exposing the page that contains the fields that the user must update. Call the code. For example: -```AL +``` Local IsXAvailable : Boolean If field X <\> '' then Exit(True) @@ -31,9 +31,11 @@ Open the card page in edit mode If not IsXAvailable then Error(Field X is missing a value. Please correct it.) +``` The calling code +``` .. SetupTable.VerifyAndSetX @@ -42,17 +44,18 @@ SetupTable.VerifyAndSetX ``` ## NAV Specific Example -**** - In the **Sales & Receivables Setup** table (311) for the DK version, the following procedures have been added: +```al Local Procedure IsOIOUBLPathAvailable(...) Procedure VerifyAndSetOIOUBLPath(...) +``` The code in the **Sales & Receivables Setup** table can now be called directly from the related processing codeunit, such as the **Sales-Post + Print** codeunit (82). Were the code is called: + ```AL IF ("EAN No." <\> '') THEN diff --git a/content/NAVPatterns/1-patterns/error-message-processing/index.md b/content/NAVPatterns/1-patterns/error-message-processing/index.md index ea38af64..2b736c8c 100644 --- a/content/NAVPatterns/1-patterns/error-message-processing/index.md +++ b/content/NAVPatterns/1-patterns/error-message-processing/index.md @@ -53,13 +53,14 @@ When the processing is complete, you can check if any error messages of type "Er The code below is an example of how the error message component was used in one part of the before mentioned Mexican feature. This code iterates over all G/L Accounts and pipes information out into an XML file. While doing so, it is validated that all mandatory fields have values and meet certain conditions. And only if that is the case, is the XML document actually exported. Also notice, that an error message is logged, in case no G/L Accounts are found given the provided filters. That way, the user can be guided to setup the system correctly. ```AL - PROCEDURE ExportChartOfAccounts@1(Year@1000 : Integer;Month@1001 : Integer); - VAR - TempErrorMessage@1003 : TEMPORARY Record 700; - BEGIN +PROCEDURE ExportChartOfAccounts@1(Year@1000 : Integer;Month@1001 : Integer); +VAR + TempErrorMessage@1003 : TEMPORARY Record 700; +BEGIN TempErrorMessage.ClearLog; // only necessary if variable is global ... CreateXMLHeader(Document,RootNode,CatalogoNodeTxt,Namespace,Year,Month,'1.1'); + IF GLAccount.FINDSET THEN BEGIN REPEAT TempErrorMessage.LogIfEmpty (GLAccount,GLAccount.FIELDNO(Name),TempErrorMessage."Message Type"::Error); @@ -73,18 +74,18 @@ The code below is an example of how the error message component was used in one GLAccount."Debit/Credit"::Credit: XMLDOMManagement.AddAttribute(Node,'Natur','A'); ELSE - TempErrorMessage.LogMessage( - GLAccount,GLAccount.FIELDNO("Debit/Credit"),TempErrorMessage."Message Type"::Error, - STRSUBSTNO(GLAccountTypeErr,GLAccount."Debit/Credit",GLAccount.RECORDID)); + TempErrorMessage.LogMessage( + GLAccount,GLAccount.FIELDNO("Debit/Credit"),TempErrorMessage."Message Type"::Error, + STRSUBSTNO(GLAccountTypeErr,GLAccount."Debit/Credit",GLAccount.RECORDID)); END; UNTIL GLAccount.NEXT = 0; END ELSE TempErrorMessage.LogSimpleMessage(TempErrorMessage."Message Type"::Error,NoSATAccountDefinedErr); - + IF NOT TempErrorMessage.HasErrors(TRUE) THEN SaveXMLToClient(Document,Year,Month,'CT'); - TempErrorMessage.ShowErrorMessages(FALSE); - END; + TempErrorMessage.ShowErrorMessages(FALSE); +END; ``` One could also do pre-processing in a function of its own, and only if the pre-processing results in no error messages of type "Error" would the processing continue. @@ -95,9 +96,9 @@ By using this easy to use component, we have the possibility to extend this func -[anchor0]: /nav/w/designpatterns/124.journal-error-processing.aspx +[anchor0]: /navpatterns/1-patterns/journal-error-processing/ [anchor1]: image001.png -[anchor2]: /nav/w/designpatterns/104.easy-update-of-setup-or-supplementary-information.aspx +[anchor2]: /navpatterns/1-patterns/easy-update-of-setup-or-supplementary-information/ [anchor3]: image003.png diff --git a/content/NAVPatterns/1-patterns/extending-the-role-center-headlines/index.md b/content/NAVPatterns/1-patterns/extending-the-role-center-headlines/index.md index ef4245f3..deffbde9 100644 --- a/content/NAVPatterns/1-patterns/extending-the-role-center-headlines/index.md +++ b/content/NAVPatterns/1-patterns/extending-the-role-center-headlines/index.md @@ -4,9 +4,7 @@ weight = 560 +++ _By David Bastide at Microsoft Development Center Copenhagen_ -_[![ ][image0]][anchor0]_ - -__ +[![ ][image0]][anchor0] ## Context @@ -19,14 +17,9 @@ This document provides an elegant and extensible pattern about how to extend the The Dynamics 365 Business Central release (April 2018) introduces a new HeadlinePart page type. This page type defines a page that rotates a display of several headlines after another, in the web client. A user can also click to switch to another headline. Headlines can also include a drilldown action that will be invoked when the user clicks the headline Text of the payload can be emphasized. Headlines are divided in 2 parts: the qualifier, and the payload as you can see in the figure below. -_ -_ [![ ][image1]][anchor1] -< --\[if gte mso 9\]\>< \[endif\]--\>< --\[if gte mso 9\]\>< \[endif\]--\>< --\[if gte mso 9\]\>< \[endif\]--\>< --\[if gte mso 10\]\> -< \[endif\]--\> - _Figure 1: Qualifier, Payload and emphasized text._ ## Usage @@ -61,89 +54,88 @@ To format headlines, you should use Codeunit 1439 Headline Management functions: ### Examples: -#### 1\. Extending the page with a new headline: - - group(LargestSale) - { +#### 1. Extending the page with a new headline: +```al +group(LargestSale) +{ Visible = LargestSaleVisible; ShowCaption=false; Editable=false; field(LargestSaleText;LargestSaleText) { - ApplicationArea = Basic, Suite; - DrillDown=true; - - trigger OnDrillDown() - var - EssentialBusHeadlineMgt: Codeunit "Essential Bus. Headline Mgt."; - begin - EssentialBusHeadlineMgt.OnDrillDownLargestSale(); - end; + ApplicationArea = Basic, Suite; + DrillDown=true; + + trigger OnDrillDown() + var + EssentialBusHeadlineMgt: Codeunit "Essential Bus. Headline Mgt."; + begin + EssentialBusHeadlineMgt.OnDrillDownLargestSale(); + end; } - } - +} +``` -#### - -#### 2\. Subscribing to the OnComputeHeadlines event, and computing headlines - - \[EventSubscriber(ObjectType::Codeunit, Codeunit::"Headline RC Business Manager", 'OnComputeHeadlines', '', true, true)\] +#### 2. Subscribing to the OnComputeHeadlines event, and computing headlines +```al +[EventSubscriber(ObjectType::Codeunit, Codeunit::"Headline RC Business Manager", 'OnComputeHeadlines', '', true, true)] procedure OnComputeHeadlinesBusinessManager() begin // \[...\] compute headline, and init the EssentialBusinessHeadline record if not ShowHeadline then - exit; // not enough data to compute headline + exit; // not enough data to compute headline + if not HeadlineManagement.GetHeadlineText( - 'Insight from last week', - StrSubstNo('The largest posted sales invoice was for %1', - HeadlineManagement.Emphasize(Format(CustomerLedgerEntry.Amount, 0, TypeHelper.GetAmountFormatWithUserLocale('$')))) - EssentialBusinessHeadline."Headline Text") + 'Insight from last week', + StrSubstNo('The largest posted sales invoice was for %1', + HeadlineManagement.Emphasize(Format(CustomerLedgerEntry.Amount, 0, TypeHelper.GetAmountFormatWithUserLocale('$')))) + EssentialBusinessHeadline."Headline Text") then - exit; + exit; + EssentialBusinessHeadline.Validate("Headline Visible", true); EssentialBusinessHeadline.Modify(); - end; - +end; +``` -#### +#### 3. Subscribing to the OnIsAnyExtensionHeadlineVisible event -#### 3\. Subscribing to the OnIsAnyExtensionHeadlineVisible event - - \[EventSubscriber(ObjectType::Page, Page::"Headline RC Business Manager", 'OnIsAnyExtensionHeadlineVisible', '', true, true)\] - procedure OnIsAnyExtensionHeadlineVisible(var ExtensionHeadlinesVisible: Boolean) - var +```al +[EventSubscriber(ObjectType::Page, Page::"Headline RC Business Manager", 'OnIsAnyExtensionHeadlineVisible', '', true, true)] +procedure OnIsAnyExtensionHeadlineVisible(var ExtensionHeadlinesVisible: Boolean) +var EssentialBusinessHeadline: Record "Essential Business Headline"; AtLeastOneHeadlineVisible: Boolean; - begin +begin EssentialBusinessHeadline.SetRange("Headline Visible", true); EssentialBusinessHeadline.SetFilter("Headline Name",'%1|%2|%3|%4|%5', - EssentialBusinessHeadline."Headline Name"::LargestOrder, - EssentialBusinessHeadline."Headline Name"::LargestSale, - EssentialBusinessHeadline."Headline Name"::BusiestResource, - EssentialBusinessHeadline."Headline Name"::MostPopularItem, - EssentialBusinessHeadline."Headline Name"::SalesIncrease, - EssentialBusinessHeadline."Headline Name"::TopCustomer); + EssentialBusinessHeadline."Headline Name"::LargestOrder, + EssentialBusinessHeadline."Headline Name"::LargestSale, + EssentialBusinessHeadline."Headline Name"::BusiestResource, + EssentialBusinessHeadline."Headline Name"::MostPopularItem, + EssentialBusinessHeadline."Headline Name"::SalesIncrease, + EssentialBusinessHeadline."Headline Name"::TopCustomer); + AtLeastOneHeadlineVisible := not EssentialBusinessHeadline.IsEmpty(); // only modify the var if this extension is making some headlines visible, setting to false could override some other extensions setting the value to true if AtLeastOneHeadlineVisible then - ExtensionHeadlinesVisible := true; - end; - + ExtensionHeadlinesVisible := true; +end; +``` #### -#### 4\. Setting the headline text on the page +#### 4. Setting the headline text on the page - trigger OnAfterGetRecord() - begin +```al +trigger OnAfterGetRecord() +begin EssentialBusinessHeadline.GetHeadline(EssentialBusinessHeadline."Headline Name"::LargestSale); LargestSaleVisible := EssentialBusinessHeadline."Headline Visible"; LargestSaleText := EssentialBusinessHeadline."Headline Text"; - end; +end; +``` - - - [![ ][image2]][anchor2] _Figure 2: Sequence diagram of headline usage_ @@ -152,10 +144,6 @@ _Figure 2: Sequence diagram of headline usage_ * Essential Business Headlines extension -< --\[if gte mso 9\]\>< \[endif\]--\>< --\[if gte mso 9\]\>< \[endif\]--\>< --\[if gte mso 9\]\>< \[endif\]--\>< --\[if supportAnnotations\]--\>< --\[endif\]--\>< --\[if gte mso 10\]\> -< \[endif\]--\> - - [anchor0]: 3733.logo.png [anchor1]: Headline.png diff --git a/content/NAVPatterns/1-patterns/hooks/index.md b/content/NAVPatterns/1-patterns/hooks/index.md index 45a11bb4..5d4b6a45 100644 --- a/content/NAVPatterns/1-patterns/hooks/index.md +++ b/content/NAVPatterns/1-patterns/hooks/index.md @@ -72,13 +72,13 @@ Suppose, we want to add business logic just before posting a sales document. In This pattern can be used in all cases to put business logic. But I see three possible approaches 1. You only declare the most important and most used hooks - * This way, you limit the amount of objects and hooks to only a few in the default product - * For example, only the OnBeforePostSalesHeader, OnBeforeReleaseSalesDocument, .. And no field validation or such... + * This way, you limit the amount of objects and hooks to only a few in the default product + * For example, only the OnBeforePostSalesHeader, OnBeforeReleaseSalesDocument, .. And no field validation or such... 2. Only at objects of the default application which you are customizing. - * This way, you don't want to create hooks for your own objects, only default existing objects. + * This way, you don't want to create hooks for your own objects, only default existing objects. 3. (recommended) You create hooks on all places you don't want to write business logic, and on all existing objects which you would like to customize. - * This is a very consistent way of working, as in any case, your business logic ends up in either a hook, or in its corresponding objects from a specific design pattern. But you know that the entry point is always a hook. - * You know what to expect in any case, both changed business logic in existing code and business logic in newly created code is entered from a hook. + * This is a very consistent way of working, as in any case, your business logic ends up in either a hook, or in its corresponding objects from a specific design pattern. But you know that the entry point is always a hook. + * You know what to expect in any case, both changed business logic in existing code and business logic in newly created code is entered from a hook. ## Related Topics diff --git a/content/NAVPatterns/1-patterns/implementation-of-surrogate-keys-using-autoincrement-pattern/index.md b/content/NAVPatterns/1-patterns/implementation-of-surrogate-keys-using-autoincrement-pattern/index.md index ee83d4f6..e8bce739 100644 --- a/content/NAVPatterns/1-patterns/implementation-of-surrogate-keys-using-autoincrement-pattern/index.md +++ b/content/NAVPatterns/1-patterns/implementation-of-surrogate-keys-using-autoincrement-pattern/index.md @@ -4,7 +4,7 @@ weight = 630 +++ _By [Soren Klemmensen][anchor0], [_Partner-Ready-Software_ ][anchor1] & [360 Visibility][anchor2]_ -## **Abstract** +## Abstract This Pattern is meant to create generic & reusable links between tables. The goal is to have an easy generic way to link a generically designed sub table to a record on a main table which can be used for other links too. @@ -13,7 +13,7 @@ To minimize the impact of customizations and to keep modules as generic and reus * To create a generic and reusable auto generated link (A Surrogate Key), Immune to natural key data & requirement changes, on a main table with minimum impact on the table. * To create generic and reusable sub tables that effortless can be reused anywhere in the application. -## **Description** +## Description Over years of development many things are repeated across different implementation and even inside the same application. A typical example could be adding comments to an area just as it is done in Microsoft Dynamics NAV multiple times. There can be reasons for doing this again and again, but not only does this need to be maintained and upgraded over the years, but all the implementations of comments also needs to be tested separately. If a standard and generic comment could be developed and a generic way of connecting it to a main table this could be resolved. This is exactly what this pattern is trying accomplish. @@ -21,25 +21,19 @@ Over years of development many things are repeated across different implementati _Figure 1: Table structure for linking a Document Header and Line Table with a Document Comment Table._ -__ - -_[![ ][image1]][anchor4]_ +[![ ][image1]][anchor4] _Figure 2: Table Structure for linking a Master Data Table with a Master Data Comment Table_ -__ - A typical way of linking a table to master data or to a document has been to use the primary key of the table being linked to. This causes some issues as the linked table now is designed specifically for the main table and it functionality cannot be reused. In case of renames the linked table needs to be renamed too which is costly in processing. Code also needs to be added on the delete trigger of the table to ensure that the attached records get removed if needed which increases the over all footprint of any change. -_[![ ][image2]][anchor5]_ +[![ ][image2]][anchor5] _Figure 3: A Generic Way of creating a Comment table and linking it in a generic way to the main table no matter what this table might be. The Unique Record Identifier on the main tables is an Integer with AutoIncrement set to yes._ -__ - It is recommended using this pattern in all tables which need sub tables unless specific reasons exists for not doing this. -## **Usage** +## Usage **Step 1**: Create a generic Unique Record Identifier in the main table @@ -77,13 +71,10 @@ Let's assume we would like to create comment for a larger number of very differe **Update the Customer table (Step 1)**: Go to table 18 Customer and add Field 50000 Unique Record Identifier. Set the Property Data Type to Integer, Editable to No & AutoIncrement to Yes. Save the changes. -_[![ ][image3]][anchor6]_ +[![ ][image3]][anchor6] -__ +_[![ ][image4]][anchor7] -_[![ ][image4]][anchor7]_ - -__ **Create Comment table (Step 2)**: Create a new table called Comment. Add 3 fields Table No., Unique Record Identifier & Line No. all of Data Type Integer. Make these 3 fields into the primary key for the table. Add a 4 field called Comment with Data Type Text(80). Save the changes. @@ -91,34 +82,24 @@ __ **Create the Comments page (Step 3)**: Start the page wizard and create a Page based on table comment created above as a List only showing the Comment field. Set AutoSplitKey property to Yes on the page. -__ - _[![ ][image6]][anchor9]_ -__ - **Update the Customer Card (Step 4)**: Add an action to the Customer Card to open the Comments. Go to Actions and create an action called Smart Comment. Populate the properties RunObject, RunPageView and RunPageLink as see in the picture below. [![ ][image7]][anchor10] -__ - **Create a hook OnDatabaseDelete (Step 5)**: Create a codeunit 50000 called "ApplicationManagement Hook" with one function called OnAfterOnDatabaseDelete taking the parameter RecRef of Data Type RecordRef. Add it as a global variable to Codeunit 1 ApplicationManagement and call the function as the last line in OnDatabaseDelete. Please read about the hook pattern before implementing it. [![ ][image8]][anchor11] -__ - **Create the code needed to delete comments linked to a deleted record (Step6)**: Create a DeleteComments function taking the RecRef of Data Type RecordRef and add the code as shown blow. [![ ][image9]][anchor12] -__ - It is assumed in this example for simplicity that the Field 50000 is reserved across the entire application for the Unique Record Identifier as defined in Step 1\. The comments are now fully working if we look away from the fact that we did not deal with a few things specific to Sales orders like RecreateSalesLines function, Archiving and Copy Document. All of which can easily be dealt with. @@ -133,24 +114,22 @@ Upgrade wise there can be an impact if data is moved out of tables to be reinser Other risks could be if Transfer Fields are used and data is being copied unintentionally. -## **NAV Usages** +## NAV Usages This is a new pattern not currently used in Microsoft Dynamics NAV. -## **Ideas for improvement** +## Ideas for improvement The reason for doing it this way is that you now can reuse your code again and again with only creating the same link on other tables instead of building it from scratch each time reducing testing needed and improving the quality of the overall product. -## **Consequences (When it should not be used)** +## Consequences (When it should not be used) This can be used on any table when linking anything to it that can be considered a generic module which can be reused. That said it should not be used in cases where there is a risk of tables growing so much that performance could be impacted. There are ways to reduce performance impact when using high volume tables, but that is outside the scope of this pattern. -## **Related Topics** +## Related Topics This is related to the hook pattern as far as they both try to reduce the footprint changes have on the standard application, by creating reusable ways to interact with the standard code. That said the hook pattern is more about hooking the functionality to existing while this pattern is more about creating reusable ways of creating functionality. -__ - [anchor0]: http://mvp.microsoft.com/en-us/mvp/Soren%20Klemmensen-5001002 "Soren Klemmensen" diff --git a/content/NAVPatterns/1-patterns/instructions-in-the-ui/index.md b/content/NAVPatterns/1-patterns/instructions-in-the-ui/index.md index 5e768c32..a1ec6258 100644 --- a/content/NAVPatterns/1-patterns/instructions-in-the-ui/index.md +++ b/content/NAVPatterns/1-patterns/instructions-in-the-ui/index.md @@ -74,19 +74,27 @@ Important parts: Example of the embed code: +```js