0

Let's say I've extended the CRM FormContext by creating a new TS class that wraps it, and adds helper functions.  This new ExtendedContext has functions like getDisplayValue(attName) which gets the attribute, handles it not being on the form, determines the attribute type, and appropriately returns what the "displayed value" is.  As I add more helper functions, the class get's bigger and bigger, which means I need to start splitting the class into more classes, but I don't want the API to change. The consuming code should not have to know that it needs to create a DisplayExtendedContext class to call getDisplayValue, all functions should exist on the main extended context. What's the recommended approach?

My current approach feels wrong and looks like this:

// extendedContex.ts
import { DisplayContext } from "../context/display";

export class ExtendedContext implements XrmExt.ExtendedContext {
  public context: Xrm.FormContext // Actual Xrm.Client form context
  private display: DisplayContext;
  
  constructor(context: Xrm.FormContext){
    this.context = context;
    this.display = new DisplayContext(this);
  }
  
  public getDisplayValue(att: string): string {
    return display.getDisplayValue(att);
  }
}

// xrmExt.d.ts
declare namespace XrmExt {
    interface ExtendedContext {
        getDisplayValue(att: string): string;
    }
}

// ../context/display.ts
export class DisplayContext {
    private context: XrmExt.ExtendedContext;
    
    constructor(context: XrmExt.ExtendedContext){
        this.context = context;
    }
    
    public getDisplayValue(att: string): string {
        // Do logic here, with full access to the ExtendedContext
    }
}

Here are the issues it has:

  1. I have to duplicate the pass throughs for ExtendedContext functions. So every function I add, I have to implement it in the smaller context class, and then add it as a pass through in the ExtendedContext class and to the ExtendedContext interface.  I'm lazy, I don't want to have to do that for every function.
  2. This one is more minor, but the ExtendedContext that is passed to the DisplayContext is not fully initialized, which could lead to null ref errors.  For example, if DisplayContext were to call a function on the XrmExt.ExtendedContext interface in it's constructor that it itself implements, the class level "display" field of the ExtendedContext class will not be populated, and a null ref exception would be thrown.  An "unspoken" rule of never access the ExendedContext from the constructor of one of the smaller classes would prevent this from ever being an issue. I'm guessing Mixings might be the way forward here, but I'm just not sure.  Thoughts / Suggestions?
Everett
  • 8,746
  • 5
  • 35
  • 49
Daryl
  • 18,592
  • 9
  • 78
  • 145
  • So ... iinheritance? (If not, why not?) – Tibrogargan Jun 04 '21 at 00:36
  • I need multiple inheritance. I'll have multiple classes that should combine into API. I guess I could have chained inheritance, but that would be kind of weird... – Daryl Jun 04 '21 at 00:52
  • Is there a advantage to this approach over just importing the helpers where needed - passing in formContext when called? This would then support tree shaking. You could import the whole module to get intellisense support for the functions available. Is it because you have some state that is maintained in your extended context - meaning you need an instance created? In your code snip - the only state is DisplayContext - does this hold state? – Scott Durow Jun 04 '21 at 02:03
  • There may be some state in some situations, but for the most part there isn't. Primarily the reason I don't pass in the form context is because it would get annoying to pass it in to every function call. Having things non static does make it easier to test. – Daryl Jun 04 '21 at 04:40
  • Hi Daryl, I still have yet to learn TS, but in C# I would use `partial` class declarations. Does TS have those? For example in C# I often extend proxy classes with additional `partial` declarations in separate files. For example, I'd put general functionality, in a `partial` declaration file of `Contact+.cs`. And if I have specific functionality for that proxy class I might add `Contact+Email.cs`, `Contact+Merge.cs`, and `Contact+Hierarchy.cs` each containing `partial` declarations with the relevant members. – Aron Jun 08 '21 at 00:41
  • Yes, Aron, that is basically what I would use in C#, but there is no equivalent in TS – Daryl Jun 08 '21 at 01:16
  • Gotcha, thanks. I have heard it said that valid JavaScript code is also TypeScript code. Maybe look at coding this particular class as straight JS across multiple files... as [this question](https://stackoverflow.com/questions/5998944/is-it-possible-to-give-javascript-partial-class-behavior-like-c-sharp-or-monkey) discusses. – Aron Jun 08 '21 at 14:16
  • @Aron you can create the Typescript that executes the JavaScript, but you can't setup the TypeScript to define the classes/types to get the intellisense that I'm expecting with that approach. – Daryl Jun 09 '21 at 16:22

1 Answers1

0

Would Typescript Mixins be an option Daryl?

Maarten Docter
  • 1,029
  • 1
  • 13
  • 31
  • I did attempt this, but it is just a really weird way to setup the connections, and feels like it isn't any better than chaining connection inheritance. Plus getting access to the other functions in other class files is janky – Daryl Jun 04 '21 at 13:15
  • I also was unable to set this up in a manner that didn't create circular import references – Daryl Jun 04 '21 at 15:25