Dates. What a nightmare. Day/Month/Year? Month/Day/Year? 24 hour time? 12 hour time? It’s almost enough to make you sympathetic to the idea of decimal time…almost.
Type Helper codeunit to the rescue. It has a method to allow you to evaluate the text of a date, time or datetime into the corresponding type according to a format that you specify.
AVariant := DateResult;
FormatString := 'ddMMyy';
if TypeHelper.Evaluate(AVariant, DateText, FormatString, '') then
DateResult := AVariant
The first parameter is of type Variant. The actual data type that the variant contains determines whether the method will attempt to evaluate to a date, time or datetime. Unfortunately because that parameter is passed by reference (var) you have to declare a variant variable and then assign its value to another variable afterwards – but apart from that its pretty self explanatory.
Use table extensions to extend the OnModify trigger rather than OnBefore/AfterModify subscriptions where possible. If you must use subscribers then be aware of some of the unexpected situation they are called in.
One of those situations is when a related table has been renamed. The Modify events are thrown in secondary tables e.g. if an Item record is renamed than all of its Item Ledger Entry records will have OnBeforeModifyEvent and OnAfterModifyEvent fired.
If you need to hang some custom logic off the back of a record modification (in a standard table) then I think you’ve got three main options:
Create a table extension and add an OnModify trigger
Add a subscription to the OnBeforeModifyEvent or OnAfterModifyEvent for the table in question
Subscribe to the events in the Global Triggers codeunit to set the table mask and listen for modifications on the table you are interested in
In general I think that is the order of preference i.e. if you can use a table extension, do.
Why Use Events Then?
This isn’t really the point of this post – but why might you use events then. In short, when you can’t use a table extension. That will usually be for one of two reasons:
The modify trigger isn’t called i.e. the base app calls Modify(false)
You don’t know which tables you want to work with at design time – your app has got some setup to determine which tables to support maybe in some integration scenario or like Change Log Setup
Considerations For Modify Subscriptions
OK, you’ve decided to use event subscriptions to the OnBefore/AfterModify events. Now to the point of the post. There are some things you need to be aware of:
They are called for temporary records – most of our subscribers start with a Rec.IsTemporary() check
They are called whether or not the modify trigger has been called – the RunTrigger parameter indicates whether Modify(true) or Modify(false) was called
You need to explicitly call Rec.Modify if you make any changes to Rec with an OnAfterModifyEvent subscription otherwise your changes will not be persisted
What’s in a Rename?
You probably knew all of that anyway, What I didn’t know until today is that the events are fired if a parent table is renamed. For example, if you rename an Item record then these events will be fired for each Item Ledger Entry record. Which makes sense and might be exactly what you want.
We hadn’t thought of that and it was the cause of a bug in our app. Shame on us.
This is an intro post to the Error Message Mgt. codeunit and related objects. NAV has never brilliant when it comes to error handling, for a couple of reasons.
The error messages themselves sometimes leave a lot to be desired
The whac-a-mole nature of fixing multiple errors by finding one at a time and attempting to post/register again
There isn’t a lot we can do about the standard error messages, but we can write more considerate errors for our users. Describe the problem and guide the user to the solution as much as possible i.e. not “There was nothing to handle”.
What about #2? What if we could line up all the moles so that the user could whack them all in one go?1 We can use the Error Message Management objects to do just that.
This is a useful post you could take a look at – http://www.mynavblog.com/2019/04/09/how-to-write-error-and-confirm/ – but even having read that I still struggled my way through how to use it and write tests around it. I didn’t find the framework particularly easy or intuitive to work with so hope I can save someone else some head-scratching.
1 metaphorical moles. I’m not endorsing whacking actual moles.
You might want to use this framework when you’ve got some process that could throw errors for multiple different reasons. Usually these are going to be some posting or registering routine for a journal line or a document.
Often there are all sorts of things that can wrong with those routines – posting date ranges, dimension errors, mandatory fields that have not been populated, missing posting setup, missing no. series… blah blah blah.
Rather than just throwing an error for the first problem we encounter we want to collect them together so that the user can fix them before posting again.
I was going to attempt a single overview post, but I’ve decided against that. I think it will be more useful (hopefully) to work through an example – albeit a silly one – in stages. I’ve got a small app for posting a record of video calls – because that’s what we need right now, more video calls and more admin.
The app adds a journal page to record the platform (Teams, Zoom, WhatsApp or Skype) the type of call, date, duration and participants. Before the journal can be posted there is a deliberately convoluted process to check for various errors. Concisely summarised below:
No. of Participants must not be 0
Duration (mins) must not be 0
Posting Date must not be blank
Posting Date must be within allowed posting dates for the user
Zoom calls cannot be over 40 minutes when the No. of Participants is > 2 – I’m a cheapskate and have got a free account
Teams cannot be used for a call type of Family Quiz – surely no family is that corporate?
WhatsApp should not be used for groups of more than 4 – it’s bad enough with 2
WhatsApp should not be used for a call type of Customer Demo – I mean, you don’t…do you?
Skype isn’t used for more than 2 participants – I know technically it can be…it just isn’t
Family quizzes can’t be held Monday – Thursday
A call type of Daily Team Call cannot be more than 45 minutes long – you need to find a smaller team to have daily calls with
A call type of Daily Team Call cannot have more than 30 participants – see #11
While this is daft, it is an example of how a journal might not be able to be posted for lots of different reasons. Normally we expect the user to fix those errors one at a time and, if they’ve still got the will to live, post the batch when they have resolved them all.
At the moment I’m just using Testfield and Error to throw errors when a journal line is valid. Over the next few posts I’m going to see if I can the Error Message Mgt objects to build a list of errors and display them all at once. Then we’re going to talk about how to test this behaviour.
In the meantime, if you want to see an example of this sort of error collection in the base application then look at SendToPosting on the Sales Header table.
ErrorMessageMgt.PushContext(ErrorContextElement, RecordId, 0, '');
IsSuccess := CODEUNIT.Run(PostingCodeunitID, Rec);
if not IsSuccess then
Then in the Sales-Post codeunit you’ll see this kind of thing (this from CheckAndUpdate)
ErrorMessageMgt.PushContext(ErrorContextElement, RecordId, 0, CheckSalesHeaderMsg);
if GenJnlCheckLine.IsDateNotAllowed("Posting Date", SetupRecID) then
If you weren’t working remotely before 2020 then we’ve all had to guess used to it this year. Collaboration tools like Teams are invaluable for keeping up with teammates during the day, discussing work and bouncing ideas and banter off each other. Video calls, screen sharing, chat, shared documents, gifs – that’s all great – but not quite a replacement for sitting at the same desk and looking at some code together.
There is a better way.
What is Live Share?
If you’re not already using VS Code Live Share then check it out. Get the extension pack rather than just the Live Share extension itself which adds audio calling through the sharing session. Sign in with a Microsoft or GitHub account and then start a live share session. A URL is generated that you can share for others to join, from VS Code, Visual Studio or even a browser.
Others will be able to see the source code, navigate and edit it. You can see each other’s cursors and choose to follow someone or work independently. Just like real-time collaborating on a shared Google doc or Word document but in the IDE. Your own extensions, your own theme, but working on shared code.
Whether you’re into pair-programming or just occasionally want a colleague to look at your code and help with something this is a great solution. Being able to independently navigate the source code and go-to definitions is far better than simply screen sharing.
However, seeing the source code is one thing, you really want to be able to publish the changes and see them in the web client.
Sharing a Server
We develop against local Docker containers. Which is great for giving each of us complete control over our own environment. It’s not so great for sharing that environment with others. But wait, you can share your local Docker container through the Live Share session.
First, make sure that you are publishing all ports when you create the Docker container. This will publish the container’s internal ports (80, 443, 7045-7049, 8080) to ports on the host. You’ll be able to access ports inside the container via the ports they are mapped to on the host.
Run docker ps to see the which host ports the container has been hooked up to. In my example, I’m accessing the web client over http so I’m interested in what port 80 is mapped to.
0.0.0.0:60425->80 indicates that port 60425 on the host (my local machine) has been mapped to port 0 in the container. In which case, http://localhost:60425/bc should load the web client. Check that you can open a browser to that address and log in.
Now we can share that address as a shared server. Click on Share server… and enter the web client URL in the dialog box. You can give a friendly name to the shared server as well if you like.
Once you’ve shared the container anyone else who has joined the session will be able to click on the link under Shared Servers. That will load the web client to your local Docker container through the Live Share session.
I’ll say that again – it will load the web client, running in your local Docker container, through the Live Share session 👀 As far as your collaborator is concerned they just load the same URL as you (http://localhost:60425/bc) and have access.
Before you ask, I don’t know. Probably magic.
Now you can genuinely collaborate on some code, in real-time and publish to the same Docker container to see the results and test. Maybe you want to work together on some code, maybe you need want some help with a tricky bug or requirement. Last week I used it to ask a colleague to quickly test a fix for a bug that he had reported.
Use your imagination. This is just one of the many benefits that moving our development to VS Code has brought. Good times.
If you occasionally glance at my blog you might have noticed that I am a big fan of pull requests as served up by Azure DevOps (exhibit A). I briefly described our typical branching strategy here, including how and why we use pull requests.
I love it. Writing, testing and reviewing discrete pieces of development independently of one another helps spread the work around the team and prevent work getting blocked by some unrelated development in the same project.
However, occasionally we’ll have several pull requests open for the same project which I want to publish to some testing environment (either a local Docker container or SaaS sandbox) all together. Maybe it made sense to develop those work items separately but it’s hard to test them in isolation.
I tend to create a new local branch called testing or something equally banal, merge all the changes into that branch and publish. Here’s where the octopus comes in. We usually use git merge to merge a single other branch into the current branch e.g.