delegateas / XrmDefinitelyTyped

Tool to generate TypeScript declaration files for Dynamics 365/CDS client-side coding.
http://delegateas.github.io/Delegate.XrmDefinitelyTyped/
MIT License
132 stars 53 forks source link

Add separate options for field names/enums generation #127

Closed zhaparoff closed 3 years ago

zhaparoff commented 5 years ago

Is your feature request related to a problem? Please describe. Currently, I can't use generated Forms or WebEntity code, because it is umpossible to manage definition files for 150+ entities in a large project in some effective way, at least when you are using some bundler. Also, I'm using the standard @types/xrm definition, instead of generated one.

Describe the solution you'd like It would be nice to have declarations for:

  1. Field names. Some class with constants defined like:

    export class Account {
    readonly name = "name";
    readonly address1_city = "address1_city";
    ...
    }

    Optionally, entity/field description can be added as comment for class/field in generated output.

  2. Enums. Something that is already generated. But with ability to choose between usual and constant enums - since constant enums have certain pitfails when using them in different kind of TS tools.

Ideally, tool should have an ability to generate aforementioned stuff separately, specifying output folder for each one.

With these definitions it would be much easier to use standard API, but exclude possible typos in field names and enums values.

Describe alternatives you've considered Creating own tool or forking this one and implementing functionality myself. But I have lack of experience in TS code generation, so any of these will take noticeable time to accomplish.

mktange commented 5 years ago

What is the problem you are encountering when trying to manage definition files for many entities in large projects?

zhaparoff commented 5 years ago

I have a lot of JS code, which I'm going to port to TS. 100+ entities, and at least 20 of them have multiple forms defined - so totally 150+ forms.

I will use a bundler (most likely webpack or maybe rollup) to reduce a number of files for deployment, also that way I will ensure that all pieces of logic are loaded on form/ribbon. Bundler flow will include TS transpiler, which will perform typechecking. And that transpiler will use type definitions to verify that code is correct.

So, if I will generate definitions with this tool for my 100+ entities - I will have over 9000 definition files generated, for example in ./typings/xrm. Then, I will need to instruct typescript to use that definitions. But I can't simply do

  "compilerOptions": {
    "typeRoots": [
        "node_modules/@types",
        "typings/xrm"
    ]
  }

because in this case, any subfolder in typeRoots should contain index.d.ts or package.json file with definitions.

So the only working solution will be to include each single subfolder under typings/xrm into typeRoots array, and that will be almost 300 entries in my case. This is totally unmanageable solution.

Probably, I can use some additional step that will convert that folder into plain file structure (or copy files into temporary folder). But in this case name conflicts could be an issue.

Also, I'm going to give a chance to an oneFile option. But generated file is going to be really monstrous in this case.

One more issue, I can think of, not related to the initial one - this tool generates its own Xrm namespace definition, not compatible with standard SDK. So I won't be able to migrate my logic partially - I will need to rewrite ALL files to new definitions, before I will be able to build the project. Although this is not a showstopper, it still makes me doubt.

mktange commented 5 years ago

Did you try including the definition files with the include option?

{
    "compilerOptions": { ... },
    "include": [
        "typings/xrm/**/*.d.ts"
        ...
    ]
}

The typeRoots option is for files that are structured like @types packages (with index.d.ts or package.json as you mention). XrmDefinitelyTyped does not create definition files like this. Instead it creates ambient declaration files that can be accessed as global types.

Regarding the issue of it not being compatible with the "standard SDK", I assume you are talking about the @types/xrm package? If so, then yes, they will clash on types. However, I think you should be able to make them co-exist with some type-casting, if you don't want to convert your entire codebase in one go. Mixing them won't be pretty though, so I'd recommend doing it all at once.

zhaparoff commented 5 years ago

Thanks @mktange, adding type definitions using include worked for me. Meanwhile, I'm still thinking about ability to generate field names, because in some cases I need just a string, and not getAttribute/getControl method override.

magesoe commented 5 years ago

Which cases @zhaparoff? We have yet to find a case that was not solved by using form intersections

zhaparoff commented 5 years ago

Scenario 1 Let's assume I have a form with some attribute _newfieldname used in several places on form, in different tabs, in header/BPF as well. And I need to hide that field. Calling Xrm.Page.getControl('new_fieldname').setVisible(false) will hide only the first occurence of that field, remaning another ones visible, as well as the header/BPF control. Currently I have a framework function, which takes attribute name as a parameter and uses Xrm.Page.getAttribute('new_fieldname').controls.forEach() to hide/disable all attribute controls at once, also covering the header/BPF controls as well. The function itself can be implemented using Xrm.BasicPage interface, but I still have to pass some magic strings as parameters.

Scenario 2 I need to apply some logic to a quite long list of fields (200+) on some form. How can I build processing using a loop, if I don't have field names in my hand? I have to write:

Form.getControl('field1').setVisible(false);
Form.getControl('field2').setVisible(false);
...
Form.getControl('field100500').setVisible(false);

instead of:

const fields = [
  "field1",
  "field2",
  ...
  "field100500"
];
for (const f of fields) {
  Form.getControl(f).setVisible(false);
}

Second one will be much easier to verify, change and maintain, than first, as for me.

henrikhannemose commented 5 years ago

The general pattern is to not pass magic strings around. Instead pass the actual controls/attributes.

Scenario 1: Pass an attribute instead of a string:

HideControls(Form.getAttribute("my_field1"));

In your library:

export function HideControls(attr: Xrm.Attribute<any>)
{
    attr.controls.forEach(a => a.setVisible(false));
}

Scenario 2: Iterate over controls instead of strings:

let controls = [
    Form.getControl("my_field1"),
    Form.getControl("my_field2")
];

for (let control of controls) {
    control.setVisible(false);
}

I have tried to follow your examples above to illustrate the approach. As a side note: To me it is a code smell to want to show/hide 200+ fields. Consider putting them into e.g. tabs and showing/hiding tabs instead or handling the business need in another way.

zhaparoff commented 5 years ago

@henrikhannemose,

I understood, that aforementioned scenarios can be implemented using plain strings with attribute names, but what is the role of the XrmDefinitelyTyped library in this case? My expectation is to use strongly typed enums/constants/some union types, so there will be a protection against typo errors in field names or picklist values.

Regarding the second example, I agree that visibility can be tuned with other approaches. But what about disabling over 100 fields? That can't be achieved using tabs or sections.

Anyway, I'm already working on my own generator for TS definitions, based on very promising library ts-morph (https://github.com/dsherret/ts-morph). Probably, I would be able to open the code, once it will get to some working state.

henrikhannemose commented 5 years ago

Everything in my reply is strongly typed. This is one of the benefits of using XrmDefinitelyTyped. Please re-read the reply and try it out for yourself.

To elaborate, Form.getAttribute("my_field1") is only valid if there is a field called "my_field1" on the form in question.

Regarding your business need: I have never encountered a case where I wanted to disable +100 fields on a single form (that's a lot of fields in the first place). Still, the approach I listed above would also work just fine in this case.

misoeli commented 3 years ago

I fail to see the need for constant generation, what with the current method fully supporting the described scenario. Just to further underline the point previously made: While you do need a string to select the relevant attribute/control, the possible values that string can have is limited to exactly match the field names available on the entity/form.

Closing here.