1.0.0 • Published 4 years ago

ng-dynamic-form-dto-mapper v1.0.0

Weekly downloads
40
License
-
Repository
-
Last release
4 years ago

NgDynamicFormDtoMapper

This library was created to flatten a JSON template, which can be a complex object, and displays the questions dynamically.

It then takes the values and makes a DTO style object, mirroring the JSON object, back to the server. As with all things, it is best explained by example.

The questions are all displayed using Angular Material, and so the properties that the JSON template contains, and the styling, will be very familiar to Material Design users.

Quick Example

We have the following (in this case C#) BreakdownJob DTO which the backend expects to receive to later convert into a Data Model for saving to the database:

public void BreakdownJob 
{
    public DriverDetails DriverDetails { get; set; }
    public BreakdownDetails BreakdownDetails { get; set; }
}

public void DriverDetails 
{
    public string Name { get; set; }
    public int Age { get; set; }
    public int BreakdownCoverTypeId { get; set; }
    public Vehicle Vehicle { get; set; }
}

public void BreakdownDetails 
{
    public List<int> VehicleFaultIds { get; set; }
    public Address Location { get; set; }
}

public void Address 
{
    public string FirstAddressLine { get; set; }
    public string SecondAddressLine { get; set; }
    public string PostCode { get; set; }
}

public void Vehicle 
{
    public string Registration { get; set; }
    public int MakeId { get; set; }
}

You would then define a json object which is a list of questions to be displayed.

So for the above an example JSON question set would look like this:

{
  "metaData": {
  },
  "questionSet": {
    "breakdownJob": {
      "driverDetails": {
        "name": {
          "controlType": "textbox",
          "label": "Name",
          "validators": {
            "required": true,
            "minlength": 2
          }
        },
        "age": {
          "controlType": "textbox",
          "label": "Age",
          "type": "number",
          "validators": {
            "required": true,
            "min": 18
          }
        },
        "breakdownCoverTypeId" : {
          "controlType": "radio",
          "label": "Breakdown Cover Type",
          "options": [
            { "id": "0", "value": "None" },
            { "id": "1", "value": "Basic" },
            { "id": "2", "value": "Standard" },
            { "id": "3", "value": "Premium" }
          ]
        },
        "vehicle": {
          "registration": {
              "controlType": "textbox",
            "label": "Registration",
            "type": "text"
          },
          "makeId": {
            "controlType": "dropdown",
            "label": "Vehicle Make",
            "options": [
              { "id": "0", "value": "Audi" },
              { "id": "1", "value": "BMW" },
              { "id": "2", "value": "Mercedes" },
              { "id": "3", "value": "Rimac" }
            ]
          }
        }    
      },
      "breakdownDetails": {
        "vehicleFaultIds": [
          {
            "controlType": "textbox",
            "tooltipText": "Please select what is wrong with the vehicle",
            "label": "Vehicle Fault",
            "options": [
              { "id": "0", "value": "Alternator Trouble" },
              { "id": "1", "value": "Damaged Tyres/Wheels" },
              { "id": "2", "value": "Electrical Problem" },
              { "id": "3", "value": "Misfuelling" }
            ],
            "validators": {
              "required": true
            }
          }
        ],
        "location": {
          "firstAddressLine": {
            "controlType": "textbox",
            "label": "First Line of Address",
            "validators": {
               "required": true
             }
          },
          "secondAddressLine": {
            "controlType": "textbox",
            "label": "Second Line of Address"
          },
          "town": {
            "controlType": "textbox",
            "label": "Town",
            "validators": {
               "required": true
            }
          },
          "postcode": {
            "controlType": "textbox",
            "label": "Postcode",
            "validators": {
               "required": true,
               "pattern": "([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9][A-Za-z]?)))  \\s?[0-9][A-Za-z]{2})"
            }
          }
        }
      }
    }
  }
}

Import the NgDynamicFormDtoMapperModule module:

import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { NgDynamicFormDtoMapperModule } from 'ng-dynamic-form-dto-mapper';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    NgDynamicFormDtoMapperModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

In your component, you would feed this JSON to the NgDynamicFormDtoMapperComponent and listen for it's response:

export class AppComponent implements OnInit {
    public dynamicForm: any; // this can be strongly typed if required

    public constructor() {}

    public ngOnInit() {
         // make a call to retrieve the json and set it to dynamicForm;
    }
    
    public submitForm(form: BreakdownJobDto) {
        // send the form back to the server
    }
}
<lib-ng-dynamic-form-dto-mapper *ngIf="dynamicForm"  (submittedForm)="submitForm($event)" [dynamicForm]="dynamicForm"></lib-ng-dynamic-form-dto-mapper>

When the form is filled in, this is the json that will be sent back:

{
  "breakdownJob": {
    "driverDetails": {
      "name": "John Doe",
      "age": "35",
      "breakdownCoverTypeId": "2",
      "vehicle": {
        "registration": "AM61ORD",
        "makeId": "0"
      }
    },
    "breakdownDetails": {
      "vehicleFaultIds": [
        "0",
        "2"
      ],
      "location": {
        "firstAddressLine": "3 Tyddyn Ddeici",
        "secondAddressLine": "",
        "town": "Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch",
        "postcode": "LL61 5PJ"
      }
    }
  }
}

A More In-Depth Look

The main thing the user will have to do is set up the json question set. It is broken into two properties - metaData, and questionSet.

Metadata

The first property is a MetaData object, which contains configuration settings for the question set. At this moment in time, the only configuration available is to set the locale for dates:

{
  "metaData": {
      "locale": "en-GB"
  },
  "questionSet": {
  }
}

This will ensure that date controls format the date according to the locale. The default is en-US.

If you don't require any meta data to be set, you can omit the property completely from the JSON and only include the questionSet property.

Question Set

The second property is the questionSet property. This is where most of the work is. The way it works is that the questionSet will mirror exactly the structure of the DTO the server expects. For every integral type (a property that has an actual hard value), the questionSet JSON will contain a question control for that property.

So a DTO with 3 properties, each of which is an object containing 4 integral value properties, will require a questionSet with the same 3 properties, each of which contains 4 question control objects.

In our example above, the DTO had a property for DriverDetails, which was an object containing 3 integral properties (Name, Age, BreakdownCoverTypeId), and a Vehicle object which contained 2 integral properties (Registration, MakeId).

Therefore the question set JSON needs to have a DriverDetails property holding 3 question controls for the properties: Name, Age, BreakdownCoverTypeId and a Vehicle property which contains 2 question controls for the properties - Registration, MakeId.

This mirrored structure is what allows the library to map the question set values into a DTO dynamically.

Dependencies

You will need to install the following dependices

- @angular/common: >=6.1.5,
- @angular/core: >=6.1.5,
- @angular/forms: >=6.1.5,
- @angular/material: >=6.0.0,
- @angular/material-moment-adapter: >=6.0.0,
- moment: >=2.24.0

For @angular/material, you will need to install it following their specific instructions (using ng add ... and picking a theme etc)

You will also require your root module to import Angular's BrowserModule and the BrowserAnimationsModule

Question Controls

There are 4 types of question controls that are currently supported: Checkboxes, Dates, Dropdowns, Radios, and Textboxes. As many of these are self-explanatory, for the most part I will simply list an example usage with the property and an example value.

This section will contain properties that are shared among all controls, with the sopecific control section dealing with properties unique to it:

{
  "controlType": "Checkbox", 
  "label": "I have read and accept the Terms & Conditions", // The question label to display to the user
  "tooltipText": "It is important you read and understand these terms. You must select this in order to continue",
  
  "validators": {         // OPTIONAL - For more information about validation, see the validation section
    "required": true 
  }
}

Checkboxes

Checkboxes do not currently have any unique properties specific to them.

Dates

Date questions will have a calender picker that can be used to input the date. Dates have the following unique properties that can be set

{
  "max": "2020-01-01",        // OPTIONAL - The max date that can be selected
  "min": "2019-01-01",        // OPTIONAL - The min date that can be selected
  "startAt": "2019-07-14",    // OPTIONAL - The date that the calendar picker will have pre-selected
  "startView": "Year",        // OPTIONAL - The calendar picker will start off with a picker for the year.
                              // Other possible values are: year, multi-year, and month (which 
                              // is the default)
}

Dropdowns

Dropdowns have the following properties that can be set

{
  "options": [    // See the options section for more details
    { "id": "1", "value": "Display value 1" },
    { "id": "2", "value": "Display value 2" },
    { "id": "3", "value": "Display value 3" }
  ]
}

Radios

Radios have the following properties that can be set

{
  "options": [    // See the options section for more details
    { "id": "1", "value": "Display value 1" },
    { "id": "2", "value": "Display value 2" },
    { "id": "3", "value": "Display value 3" }
  ]
}

Textboxes

Textboxes have the following properties that can be set

{
  "type": "number",   // Supports all angular material types for textbox inputs
  "options": [        // OPTIONAL - See the options section for more details
    { "id": "1", "value": "Display value 1" },
    { "id": "2", "value": "Display value 2" },
    { "id": "3", "value": "Display value 3" }
  ]
}

A textbox has the options property as an optional property. When this property exists, the textbox will have an autocomplete feature automatically enabled which will filter itself as the user types.

Validation

The question control object has a class purely for validation. Most of the Angular validators are supported with the exhaustive list below:

{
  "validators": {
    "email": true,
    "max": 10,          // Maximum number allowed
    "maxlength": 15,    // Max length of a string
    "min": 3,           // Min number allowed
    "minlength": "6",   // Min length of a string
    "pattern": "([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9][A-Za-z]?))) \\s?[0-9][A-Za-z]{2})",    // Regex validator. This one is for a UK postcode
    "required": false,  
    "requiredTrue": true    // For example in the case of a checkbox question, this will ensure it is checked
  }
}

Options

Certain controls allow for the option property which is usually expressed in the form

{
  "options": [
    { "id": "1", "value": "Display value 1" },
    { "id": "2", "value": "Display value 2" },
    { "id": "3", "value": "Display value 3" }
  ]
}

The id property here is the actual value which is saved and sent back in the DTO, not the value. The value is what is displayed to the user. The id here was that that often dropdowns are prepopulated from a database and have an id associated which we will want to sent back to the server for saving using an ORM.

Even though the id property was used with DatabaseIds in mind, you can still populate it with anything that you want

How it looks

The save button is centered here as the element had a class on it to center everything. It's default places it on the left

The form first displayed:

The form with errors:

The form correctly filled in:

Further help

For any issues, you can open one on the public version of the repo: https://github.com/AWildBugAppeared/NgDynamicFormDtoMapperIssues or contact me at awildbugappeared@gmail.com.

1.0.0

4 years ago

1.0.0-4

4 years ago

1.0.0-3

4 years ago

1.0.0-2

4 years ago

1.0.0-1

4 years ago

0.9.6

4 years ago

0.9.5

4 years ago

0.9.4

4 years ago

0.9.3

4 years ago