The power of Variants

This blogpost of Arend Jan Kauffman reminded me of a topic that I wanted to write a very long time now. Namely working with Variants. Sometimes I get the remark: “what the hell are they useful for? “. And to be honest: Variants could be bloody useful in some cases. Cases to make stuff even more generic then only working with RecordRef, FieldRef and such. The keyword is “Generic” here. Just trying not to create code that is copy-pasteable (if that is a word at all) .. but try that different parts of the application will run exactly the same code.

RecordRef

When you read the above .. you immediately think of RecordRef, RecordID, FieldRef, … all those kind of things. And indeed .. Variants can help you be even more generic .. in combination with RecordRef.

The case

I’m going to try to explain this with a very simple use case which came to speak on a confcall yesterday: Opening a Card page from any record. You know, sometimes, opening a card for a record depends on the data on which card page you would like to open. If you look at the Sales list page, you see this:


What if I want to make this code a little bit (let’s say “much more”) generic/repeatable? First of all: I don’t want to code like that on a page .. and second: I can imagine that other tables/data/pages has the same kind of “Open-Card-For-Certain-Records”-issues.. .

What if …

What if I could write something like this?


On any of the pages, for any record, any place, any time, any … (one?). So, exacly the same code on the “Card” action of the “Sales List” as on the “Card” action of the “Purchase List”. You would expect you need two functions, because you’re passing either the Sales Header of Purchase Header record.

How can Variants help me in this?

Let’s use the “DrillDown” method to explain what I did.. .

This is what I the result is in my two pages:


You can see that I have exactly the same function of the same codeunit, and passing a record. A different type of record. One belongs to Sales Header, the other to Purchase Header.

When you drill down, you see that Variants is helping me on this:


As you can see: I’m handling the Variants .. and even went a small stem further. With the code above, you’re even able to not only pass different tables, but you’re also able to use that same function to pass RecordRef. How Generic does one want to be?

When you drill down to the “OpenCardPage”-function, you arrive in my codeunit where I manage all this, for all records:


This way, it’s centrally managed, one place, copy-able functions, .. and most important: re-usable functions (not like the default one on the page itself).

Is this all?

Not necessarily. There is a lot you can do with Variants. It’s not my intention to show you everything (that’s not possible), just a few thouchpoints.. .

So may be another touchpoint then?

Well .. one of the PRS “Design Patterns” is “Facade” (or at least, that’s what we call it now – might change ;-)). Typically in a Facade pattern, you define “codeunits” as pieces of “apps” you’re going to execute. Like a codeunit for a certain scale to get the weight of something during a business process. Another Scale – another codeunit.

In some cases, the setup is going to define which record you’re going to “send” with your codeunit. For example, I have some kind of “Validation Framework” where I set up which codeunits have to run on which triggers and so on.. . Set up could look like this:


Codeunits, that has to run on certain tables .. .

When you’re calling your generic codeunit (which we call the “Facade”-codeunit), it’s typical you’re getting a RecordRef again .. and passing RecordRef to a codeunit is not possible.. .

So this is what you could do:


First: create a variant of your recref

Second: Pass that variant to the codeunit.

This is somewhat similar to Arend Jan’s blog (he’s using it with pages).

Conclusion

At first .. it’s difficult to imagine what this can do for you. But in terms of “repeatability”, “upgradeability” and “maintainability” .. it is sooo powerful :-). Hopefully you agree.

If you have any other examples, please share! :-).

4.40 avg. rating (88% score) - 10 votes

Permanent link to this article: https://www.waldo.be/2013/04/25/the-power-of-variants/

42 comments

2 pings

Skip to comment form

    • Johannes Sebastian on April 25, 2013 at 9:44 am
    • Reply

    This is great, thanks for sharing with practical examples

  1. y’re welcome 🙂

    • Bogdana Botez on April 25, 2013 at 11:00 am
    • Reply

    Hey, nice to see your solution so soon after the talk yesterday, let me share this with my team.

  2. Well, I actually prepared a whole different example .. but this one was more understandable, so I took that one.
    In our inhouse solution, we have elaborated it a little bit more .. making it possible for Lists as well .. and let you choose to run it MODAL or not.. . It’s just a few lines more.
    We also use it for all the frameworks that we have developer, that are linked with RECORDID to any other functionality in NAV. Something like the default RecordLinks. The big advantage is that you can have a list of your entire table (like the RecordLink table), and drill down to the exact page of that record where the linked record is all about.. . Actually just like the Notification-functionality (drilling down to the document where you added the notification) .. but then for our own stuff :-).

    Just an idea …

  3. I use variant quite often when i need to do generic functions, the last thing i did was some sort of a try-get construct for our main solution. The problem was that we moved from a country specific implementation to a W1 implementation. Since not every field we use is available on W1 i needed a way to retrieve the field values when they are there but ignore it when not. Here is what i used:

    TryGetCode(_Record : Variant;_FieldName : Text) : Code[20]
    IF TryGetFieldRefByName(_Record, _FieldName, FRef) THEN
    IF FORMAT(FRef.TYPE) = ‘Code’ THEN
    EXIT(FRef.VALUE);

    Usage:
    “Location Code” := Framework.TryGetCode(Item, ‘Location Code’);

    I realy like the generic way, i hope you will come up with more of that stuff 🙂

      • wakestar on May 29, 2013 at 8:03 am

      Hi there
      how does the code for ‘TryGetFieldRefByName’ look like?

      • devch on May 29, 2013 at 9:14 am

      Hi wakestar, its just a simple FieldRef loop to get the field by using the field’s name, here the code:

      TryGetFieldRefByName(_Record : Variant;_FieldName : Text;VAR _FRef : FieldRef) : Boolean
      RecRef.GETTABLE(_Record);
      FOR i := 1 TO RecRef.FIELDCOUNT DO BEGIN
      _FRef := RecRef.FIELDINDEX(i);
      IF _FRef.NAME = _FieldName THEN
      EXIT(TRUE);
      END;

      • waldo on May 29, 2013 at 9:27 am
        Author

      I was just working on an example myself .. but you saved me the time. Thanks, man!

      If you’re not blogging the example, I will (and give you the full credits ovbiously) 😉

      • devch on May 29, 2013 at 9:39 am

      Hi Waldo, Feel free to use my example and blog about it. A credit with reference to my blog would be nice 😀
      Have a nice day!

      • waldo on May 29, 2013 at 9:43 am
        Author

      Will do 😉
      Thanks!

      • wakestar on May 29, 2013 at 11:27 am

      Thanks… that’s what I was expecting…

      … since there is no
      _FRef := RecRef.FIELDNAME(‘my fieldname’)
      unfortunately

  4. Smart programming is the highway to repeatability;-)

  5. Thanks for that example, “deV.ch” 🙂

    • wakestar on June 6, 2013 at 12:23 pm
    • Reply

    hi waldo

    I looked at the codeunit example again…

    To me your table (screenshot) looks like you could setup any table to run with any codeunit.

    But if I understand correctly you always have to define the record type in the target codeunit (table no property) to be able to pass the record as a variant. Which means that every codeunit can only handle one table.
    If you don’t define table no… you cannot access the variant which you pass..
    Or am I missing something?

  6. Hi Waldo,

    I noticed that your variant parameter in the code sample above is passed by reference (VAR). Are you sure that’s necessary?

    In my tests in NAV 2013, it seems records passed in as variants are *always* treated as “by value”, and recordrefs are *always* treated as “by reference”, regardless of whether the variant in question is VAR or not. As a consequence, the function’s behaviour strongly differs (in terms of active filters, temporary, active sort order), depending on whether you passed it a recordref or a record.

    In my book, that reduces not only the elegance of the solution, but also its usability…

    Would you agree?

      • waldo on August 28, 2014 at 10:37 am
        Author

      Absolutely right.

      In fact .. the example above won’t even compile in NAV 2015! :-).

      The compiler will error out when you’re passing e.g. a recref variable to a byref variant parameter. Which makes sense. You’ll only be able to pass variants to ByReference variant-parameters .. .

      Good catch 😉

  7. Unfortenately this doesn’t seem to work with pre 2013 installations unless I’m doing something wrong.
    I tried to test in 5.0 SP1 and 2009 R2 but it can’t cast a a record to variant in the function call.

      • vittorio delmedico on February 23, 2016 at 3:11 pm

      I used your code to implement Facade pattern and I receive the following error when I call the part
      PassedVariant.ISRECORD :
      RecRef.GETTABLE(PassedVariant);
      ‘Invalid table handle.The record parameter has not been properly initialized’
      Any susggestions why this doesn’t work (I use NAV 2009 R2)?

      • waldo on February 23, 2016 at 3:39 pm
        Author

      If I remember correctly, the Variant-thing doesn’t really work on NAV2009 .. . You might want to verify that, because it’s just an assumption based on a (terrible) memory 😉

      • vittorio delmedico on February 23, 2016 at 3:42 pm

      Thanks, based on the error I copied I would say definitely it doesn’t work on 2009R2, I needed to know if anybody had witnessed such error message and if tried to resolve it in any manner
      Thanks

      • Giuseppe M. on July 23, 2018 at 12:14 pm

      I got the same error only with classic client in Nav 2009 R2. With RTC client seems to work properly.

    • Erik F. on February 11, 2015 at 12:11 pm
    • Reply

    I cant believe I only found this now. Thank you so much for this Info… I’ve been searching for ways to programm more flexible codeunits for a while.

      • waldo on February 11, 2015 at 1:21 pm
        Author

      Glad you like it 🙂

    • Chrstian on February 13, 2015 at 8:50 am
    • Reply

    Have you made some experience with NAV2015 CU3? I get a compile error for type mismatch Variant := Record

      • waldo on February 13, 2015 at 9:03 am
        Author

      I haven’t implemented CU3 yet.

      But one thing pops up in my mind: check if that specific Variant is a parameter that is set up By Reference. You shouldn’t assign a record/recordref to it, because the “byref” would fail. I know NAV2015 is giving an error on that now ..

      • Chrstian on February 13, 2015 at 1:10 pm

      Hm ok, we pass it By Value and seems not to work. I’ll going to test it with CU4. Thanks for your comment

      • Chrstian on February 13, 2015 at 2:57 pm

      Same behavior in NAV2015 CU4…

    • jremaud on July 17, 2015 at 8:56 am
    • Reply

    Hi Waldo,

    I just read your post 2 years later… but it’s still actual for me!
    I am looking to your example, and I can’t figure out how/when is the variabler SalesHeader intantiate in the CU 51531, fonction OpenCardPage_SalesHeader(…)? I have to try it by myself…

    Thanks for all your post,
    Have a nice day,

      • waldo on July 17, 2015 at 9:17 am
        Author

      Y’re welcome!
      We use it every day .. and still loving it! 🙂

      • jremaud on July 21, 2015 at 3:17 pm

      Hi,
      I just write and test your example.
      And for me, one line is missing to open the card on the right record, in the OpenCardPage_SalesHeader function :

      pRecRef.settable(SalesHeader);
      WITH SalesHeader DO BEGIN

      Do you agree? Or do I miss something?
      I don’t want to be “the student who surpasses the teacher”, and want to be sure of my understanding…

      Thanks for your time

    • Heinrich Vermeulen on July 29, 2016 at 6:51 am
    • Reply

    Hi,
    We are moving a company that make rubber bales to NAV 2013R2 now. They have only a handful of items, but requires many variants.

    BUT they need to move the bales between variants without item journals, i.e. only as a warehouse movement.

    What do you think is the best way to achieve this?
    Thanks
    Heinrich

      • waldo on July 29, 2016 at 7:38 am
        Author

      I think you have the wrong impression of this blogpost. This post is about the technical datatype “Variant”, not the functional “Item Variant” :-).
      Sorry, I can’t help you with this. I suggest you put your question on the MiBuSo forum…

    • Justinas on August 9, 2016 at 10:17 am
    • Reply

    Hi, Trying this in NAV 2016 – gives compilation error, that function expects variant and I’m trying to pass a record to it..

    —————————
    Microsoft Dynamics NAV Development Environment
    —————————
    Type conversion is not possible because 1 of the operators contains an invalid type.

    Variant := Record
    —————————
    OK
    —————————

    Any ideas?

      • waldo on August 9, 2016 at 11:23 am
        Author

      my guess is that you declared your Variant parameter By Reference. IN that case, you would only be able to pass variants. If the parameter is not by reference, you can pass records.. .

      • Capone on August 9, 2016 at 11:49 am

      Have you turned of so the parameter is not marked as var (By reference)?

    • Wm. Matthew Street on October 3, 2016 at 10:17 pm
    • Reply

    How about a new Variant Function Like Ok := MyVariant.ISEQUAL(NewVariant);

    returns True with both NewVariant is the same value as MyVariant.

      • waldo on October 4, 2016 at 7:49 am
        Author

      Not a bad idea at all.. although it’s comparing objects. And when is an object equal .. might be difficult to define that.. .

    • Andre on May 18, 2017 at 12:47 pm
    • Reply

    Hi Waldo,

    thank you very much for your nice tutorial.
    I am wondering if it is possible to call functions of a RecordRef-variable?
    For example : If two tables have the same functions, with same parameters, but different code, this might be interesting. Do you know if there is some field-ref alike possibility for functions?
    I was not able to find out anything useful, neither in NAV nor in the internet.

    Thanks Andre 😉

      • waldo on May 19, 2017 at 12:13 pm
        Author

      Well, that’s not possible, I’m afraid. It should be, I totally agree, but it isn’t .. :(.

  8. Hi Waldo!
    I’ve used this very handy approach many times, but in 2018 the variant can no longer be set to VAR which severely limits what we can use it for. Do you have any info on this?

    Cheers!
    Anders

      • waldo on February 18, 2019 at 4:08 pm
        Author

      I think the compiler-error is there from before 2018, no?
      From what I know: it never worked anyway. The variant was never a “real” reference, so the compiler actually is right to not allow it.
      You might want to test the “var” in your older release, and you probably will see it’s not really behaving as such…
      (now I hope I remember this correctly … ;-))

  9. Ouch!
    Well. nobody complained so far 🙂
    I’ll go with a VAR RecRef instead

  1. […] Continue reading » […]

  2. […] Bron : Waldo’s Blog Lees meer… […]

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.