Word Management

As with the release of Microsoft Dynamics NAV 2009, I was also deeply involved in the TAP for Microsoft Dynamics NAV 2009 SP1. My primary role in the TAP is to assist ISVs and partners in getting a customer live on the new version before we ship the product.

During this project we file a lot of bugs and the development team in Copenhagen are very responsive and we actually get a lot of bugs fixed – but… not all – it happens that a bug is closed with “By Design”, “Not Repro” or “Priority too low”.

As annoying as this might seem, I would be even more annoyed if the development team would take every single bug, fix it, run new test passes and punt the releases into the unknown. Some of these bugs then become challenges for me and the ISV / Partner to solve, and during this – it happens that I write some code and hand off to my contact.

Whenever I do that, two things are very clear

  1. The code is given as is, no warranty, no guarantee
  2. The code will be available on my blog as well, for other ISV’s and partners to see

and of course I send the code to the development team in Copenhagen, so that they can consider the fix for the next release.

Max. 64 fields when merging with Word

One of the bugs we ran into this time around was the fact that when doing merge with Microsoft Word in a 3T environment, word would only accept 64 merge fields. Now in the base application WordManagement (codeunit 5054) only uses 48 fields, but the ISV i was working with actually extended that to 100+ fields.

The bug is in Microsoft Word, when merging with file source named .HTM – it only accepts 64 fields, very annoying.

We also found that by changing the filename to .HTML, then Word actually could see all the fields and merge seemed to work great (with one little very annoying aberdabei) – the following dialog would pop up every time you open Word:

clip_image002

Trying to figure out how to get rid of the dialog, I found the right parameters to send to Word.OpenDataSource, so that the dialog would disappear – but… – then we are right back with the 64 fields limitation.

The reason for the 64 field limitation is, that Word loads the HTML as a Word Document and use that word document to merge with and in a document, you cannot have more than 64 columns in a table (that’s at least what they told me).

I even talked to PM’s in Word and got confirmed that this behavior was in O11, O12 and would not be fixed in O14 – so no rescue in the near future.

Looking at WordManagement

Knowing that the behavior was connected to the merge format, I decided to try and change that – why not go with a good old fashion .csv file instead and in my quest to learn AL code and application development, this seemed like a good little exercise.

So I started to look at WordManagement and immediately found a couple of things I didn’t like

MergeFileName := RBAutoMgt.ClientTempFileName(Text029,’.HTM’);
IF ISCLEAR(wrdMergefile) THEN
CREATE(wrdMergefile,FALSE,TRUE);
// Create the header of the merge file
CreateHeader(wrdMergefile,FALSE,MergeFileName);
<find the first record>
REPEAT
// Add Values to mergefile – one AddField for each field for each record
  wrdMergefile.AddField(<field value>);
  // Terminate the line
wrdMergefile.WriteLine;
UNTIL <No more records>
// Close the file
wrdMergefile.CloseFile;

now wrdMergefile is a COM component of type ‘Navision Attain ApplicationHandler’.MergeHandler and as you can see, it is created Client side, meaning that for every field in every record we make a roundtrip to the Client (and one extra roundtrip for every record to terminate the line) – now we might not have a lot of records nor a lot of fields, but I think we can do better (said from a guy who used to think about clock cycles when doing assembly instructions on z80 processors back in the start 80’s – WOW I am getting old:-))

One fix for the performance would be to create the file serverside and send it to the Client in one go – but that wouldn’t solve our original 64 field limitation issue. I could also create a new COM component, which was compatible with MergeHandler and would write a .csv instead – but that wouldn’t solve my second issue about wanting to learn some AL code.

Creating a .csv in AL code

I decided to go with a model, where I create a server side temporary file for each record, create a line in a BigText and write it to the file. After completing the MergeFile, it needs to be downloaded to the Client and deleted from the service tier.

The above code would change into something like

MergeFileName := CreateMergeFile(wrdMergefile);
wrdMergefile.CREATEOUTSTREAM(OutStream);
CreateHeader(OutStream,FALSE); // Header without data
<find the first record>
REPEAT
CLEAR(mrgLine);
// Add Values to mergefile – one AddField for each field for each record
AddField(mrgCount, mrgLine, <field value>);
  // Terminate the line
mrgLine.ADDTEXT(CRLF);
  mrgLine.WRITE(OutStream);
CLEAR(mrgLine);
UNTIL <No more records>
// Close the file
wrdMergeFile.Close();
MergeFileName := WordManagement.DownloadAndDeleteTempFile(MergeFileName);

As you can see – no COM components, all server side. A couple of helper functions are used here, but no rocket science and not too different from the code that was.

CreateMergeFile creates a server side temporary file.

CreateMergeFile(VAR wrdMergefile : File) MergeFileName : Text[260]
wrdMergefile.CREATETEMPFILE;
MergeFileName := wrdMergefile.NAME + ‘.csv’;
wrdMergefile.CLOSE;
wrdMergefile.TEXTMODE := TRUE;
wrdMergefile.WRITEMODE := TRUE;
wrdMergefile.CREATE(MergeFileName);

AddField adds a field to the BigText. Using AddString, which again uses DupQuotes to ensure that “ inside of the merge field are doubled.

AddField(VAR count : Integer;VAR mrgLine : BigText;value : Text[1024])
IF mrgLine.LENGTH = 0 THEN
BEGIN
count := 1;
END ELSE
BEGIN
count := count + 1;
mrgLine.ADDTEXT(‘,’);
END;
mrgLine.ADDTEXT(‘”‘);
AddString(mrgLine, value);
mrgLine.ADDTEXT(‘”‘);

AddString(VAR mrgLine : BigText;str : Text[1024])
IF STRLEN(str) > 512 THEN
BEGIN
mrgLine.ADDTEXT(DupQuotes(COPYSTR(str,1,512)));
str := DELSTR(str,1,512);
END;
mrgLine.ADDTEXT(DupQuotes(str));

DupQuotes(str : Text[512]) result : Text[1024]
result := ”;
REPEAT
i := STRPOS(str, ‘”‘);
IF i <> 0 THEN
BEGIN
result := result + COPYSTR(str,1,i) + ‘”‘;
str := DELSTR(str,1,i);
END;
UNTIL i = 0;
result := result + str;

and a small function to return CRLF (line termination for a merge line)

CRLF() result : Text[2]
result[1] := 13;
result[2] := 10;

When doing this I did run into some strange errors when writing both BigTexts and normal Text variables to a stream – that is the reason for building everything into a BigText and writing once pr. line.

and last, but not least – a function to Download a file to the Client Tier and delete it from the Service Tier:

DownloadAndDeleteTempFile(ServerFileName : Text[1024]) : Text[1024]
IF NOT ISSERVICETIER THEN
EXIT(ServerFileName);

FileName := RBAutoMgt.DownloadTempFile(ServerFileName);
FILE.ERASE(ServerFileName);
EXIT(FileName);

It doesn’t take much more than that… (beside of course integrating this new method in the various functions in WordManagement). The fix doesn’t require anything else than just replacing codeunit 5054 and the new WordManagement can be downloaded here.

Question is now, whether there are localization issues with this. I tried changing all kinds of things on my machine and didn’t run into any problems – but if anybody out there does run into problems with this method – please let me know so.

What about backwards compatibility

So what if you install this codeunit into a system, where some of these merge files already have been created – and are indeed stored as HTML in blob fields?

Well – for that case, I created a function that was able to convert them – called

ConvertContentFromHTML(VAR MergeContent : BigText) : Boolean

It isn’t pretty – but it seems to work.

Feedback is welcome

I realize that by posting this, I am entering a domain where I am the newbie and a lot of other people are experts. I do welcome feedback on ways to do coding, things I can do better or things I could have done differently.

 

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

PageUp and PageDown in the Role Tailored Client

If you have been working with the Role Tailored Client you are probably aware, that it doesn’t support PageUp and PageDown in Task Pages. The reason for removing this support was, that the new User Experience is centered around starting from the List, and this way you have a better overview and can find the records you are searching for right away easier and then open the Task Page.

This is all good, but users have told us, that they would like to use PageUp and PageDown in Task Pages anyway and the reason is that they want to do work on all the records they have selected in a List Place with e.g. Delayed Sales Orders. Why does the user have to close the Task Page, select the next record and reopen the Task Page.

Another scenario is users looking through recent sales orders, to find an order where the customer bought 20 bicycles (because the user remembers this).

So, no doubt that this is something we will be looking at supporting in the future – but NAV 2009 is released, and it does NOT support PageUp and PageDown.

In this post I will explain how to make a poor-man’s PageUp and PageDown in a Page – the goal is to show how this is done, and if anybody run into a customer, with the need of having PageUp and PageDown in e.g. the Sales Order Page – then they can use this as inspiration.

It isn’t a perfect solution, but it is better than nothing. To apply it on ALL pages would be a lot of work – but to apply it on a couple of pages (where the users request it the most) might be the difference between an unsatisfied user and a happy user.

In this sample, I will add the functionality to the Sales Order List Place, which is page 9305. In the List Place we will add an action called Edit All and in the Task Page for the Sales Order (which is 42) we will add two actions: Next Record and Previous Record.

The Edit All action

First, open the Page Designer for Page 9305. Move the selected line to the line after the last object – and select View -> Actions.

In the list of actions – create a new action called Edit All.

image

In the properties of action we change two values:

image

We set the Image, so that the action has the same image as Edit, and we set the ShortCutKey to Return.

Setting the ShortCutKey to Return means that whenever you press ENTER in the list – this action is called instead of the built-in Edit. DoubleClick does the same as ENTER, so my action also gets called when double clicking a row. Ctrl+Shift+E still calls the Built-in Edit action, like when you select that action in the Menu – the only trick here is ENTER and double click – no magic.

Close the properties and hit F9 to modify the code for the actions. Unfortunately F9 doesn’t position you in the action you are modifying, so you will have to find the right action, add the following variables

image

and the following code

<Action5> – OnAction()
newrec.SETPOSITION(GETPOSITION());
newpage.SETRECORD(newrec);
//newpage.SetViewFromList(GETVIEW(true));
newpage.RUN();

Note, that the SetViewFromList method call is out commented – we haven’t created this function yet on the Sales Order page, so this function more or less does exactly the same as the normal Edit function – open the Sales Order page with the currently selected record.

But we want more…

The Card needs to know the View

In order to support Page Up and Page Down, our Task Page needs to know the Filter and sorting from the List. Both these things are in the value we get from GETVIEW, so we will create a function on the Sales Order page to receive this from our list.

Design the Sales Order page (42) and hit F9 – create a new Global Variable called View (Text[250]) and set the Include In Dataset in the properties

image

Reason for the include in dataset is that it now can be used in the property expressions on actions (we don’t want to have the Next and Previous actions enabled if you cannot do next or previous).

Also create a function called SetViewFromList like this

SetViewFromList(ViewFromList : Text[250])
View := ViewFromList;

We could also insert the following lines to our OnOpenPage (as the first lines)

IF View <> ” THEN BEGIN
CurrPage.CAPTION := View;
END;

Having done this, and saved (with compile) the Sales Order page, we can go back to the Edit All action and remove the comment from the line

newpage.SetViewFromList(GETVIEW(true));

Save and run.

Open your sales order list place, create a filter on Location Code = YELLOW and double click on an order and you will get an order with a different caption

image

A little nerdy –  but I didn’t want to create a function for making the filter human readable in this post.

So far so good…

The Next and Previous Actions

So now, our Task Page knows about the filter, which was applied on the list. We of course haven’t done anything with it yet – but the remaining should be pretty straightforward for people who knows C/AL

Create two actions (at the very end of the list – make sure to get the indent right)

Set the shortcut key of Next Record to Ctrl+PgDn and the shortcut key of Previous Record to Ctrl+PgUp. For some reason I cannot use PgUp and PgDn, the Client crashes when I try to do this with an invalid Shortcut key value – I guess we could have handled this situation a little nicer:-).

Both actions should also have an enabled expression called View <> ”

image

Before adding in the code for these actions we create another global function called NextPrev. In this function we add two local variables

image

and the following code

NextPrev(Ascending : Boolean)
newrec.SETVIEW(View);
newrec.SETPOSITION(Rec.GETPOSITION());
newrec.ASCENDING(Ascending);
newrec.NEXT(1);
IF Rec.GETPOSITION() = newrec.GETPOSITION() THEN EXIT;
CurrPage.CLOSE();
newpage.SetViewFromList(View);
newpage.SETRECORD(newrec);
newpage.RUN();

Basically what we do is to locate the next record (ascending or descending) that matches the filter – and if this record is different from the current record – it closes the current page and opens a new page with the next or previous record (it is not possible to change the current record on a page).

It also transfers the View to the next page, so that Next and Previous still works.

The code for the actions should be straight forward

<Action3> – OnAction()
NextPrev(TRUE);

<Action5> – OnAction()
NextPrev(FALSE);

Save, Compile and run the Role Tailored Client.

Yes – it does some flickering when the page closes and reopens – but it works.

Note that if you move the window and use next and previous, then the next window will open on the currently saved position (the CLOSE doesn’t complete and save the new location as the preferred location until after the new page opens) so the window will jump back and forth – I haven’t found any way to avoid that.

Enjoy

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV

Microsoft Dynamics NAV 2009 launched!

Wednesday this week we officially launched Microsoft Dynamics NAV 2009 at Convergence EMEA – what a relief. We have been working hard on this release for a long time and although you always know what you would have made better when you launch a product, I think that NAV 2009 is a great release and expect a lot of this product.

Kirill Tatarinov opened Convergence EMEA with his Keynote on Wednesday and talked a lot about the current situation for the Microsoft and the partners. He talked about how it is more necessary now than ever before to stay connected to help each other through this difficult crisis. But the keynote was not all about the crisis – he also announced the launch of Microsoft Dynamics NAV 2009 – and we saw a demo of how a couple of personas was running the Role Tailored Client in NAV, we saw a forklift come on stage with packages and we saw how a mobile device was directly connected to NAV 2009 registering arrived packages in the Warehouse. We also saw how personalization can change NAV 2009 to become not just any other NAV – but your own personal NAV 2009.

There were a lot of other sessions around NAV 2009. Some about Web Services, some about warehousing, financial management, etc. etc.

Two of the sessions was about the TAP (Technical Adoption Program) and was called meet the partners and meet the customers. In these sessions people had the opportunity to ask questions to the partners and customers who had been part of the TAP.

The TAP had a goal of having 3 customers live on NAV 2009 before RTM. We smashed that goal and today, 10 customers in US, Denmark and Germany are live on NAV 2009. Dan Brown said in his keynote that we have over one year of server up-time before the product RTM’s – this is something completely new for NAV.

Some of the statements from partners and customers I noted was:

  • I don’t think we have had one single crash in NAV 2009 since we went live on September 11th 2008.
  • If I would do one thing different – I would only run the Role Tailored Client. Some users have stayed on the Classic Client and I think that the only reason for this is, that they have the option.
  • The users are more productive in the Role Tailored Client.
  • The help we have gotten through Microsoft in the TAP has been fantastic.
  • The users who only use NAV for 30 minutes a day have a harder time adjusting to the Role Tailored Client.
  • Some of the users who rely on fast data entry prefer the Classic Client.

A lot of positive feedback – and some negative. Of course we listen more to the negative feedback (as this helps us find out what we need to do better in the future), and I think it is fair to say that we have been doing a lot of investigation in order to be sure, that we know what we need to do.

So, if you ask me whether the product is ready – I would say it is ready. If you ask me whether we know how to make the product better – I would say yes – but don’t we always, that is kind of the essence of product development.

Always working on the next version.

Stay tuned.

Freddy Kristiansen
PM Architect
Microsoft Dynamics NAV