formio / angular

JSON powered forms for Angular
https://formio.github.io/angular-demo
MIT License
619 stars 464 forks source link

Custom Data Src not working in Angular version [Question] #695

Closed learner291 closed 3 years ago

learner291 commented 3 years ago

I have implemented a form which works fine in formio.js. I have a requirement to migrate it to angular version. I have done majority of the converstion, however I am stuck on one item for the last couple of days and would appreciate if someone can help.

The fiddle (click) shows the working version on formio.js. The issue is with the way I am configuring custom data source. In my formio.js, I have declared a getGenderValues function and set it as the data source for my select component as shown below, full working code is in above mentioned fiddle. This works fine on formio.js

"dataSrc": "custom",
                    "data": {
                        "custom": "values = getGenderValues()"
                    },

I am using the same schema in Angular version but I get an error shown below.

image

If I use values = this.getGenderValues(), then I receive the below error.

image

I even tried putting angular brackets "custom": "{{ values = this.getGenderValues() }}" but still getting the same error.

If I use "custom": this.getGenderValues() (i.e. without the quotation and values) then the function is called and values are displayed, however this is not pure json as this does not parse as pure json when I retrieve the schema from the server. Newtonsoft.Json.JsonReaderException is thrown when server tries to convert into json data if this.getGenderValues is not in quotes, so I do need to put it in quotes.

Newtonsoft.Json.JsonReaderException: Error parsing boolean value. Path 'components[0].components[5].data.custom'

I am not sure why keeping it in quotations is working in formio.js but not in angular version. I need to use the custom function as I need to manipulate data in the function based on some conditions so I cannot move away from it. Can someone help please?

export class AssessmentComponent {
 myForm = {
 "display": "wizard",
    "settings": {
        "pdf": {
            "id": "1ec0f8ee-6685-5d98-a847-26f67b67d6f0",
            "src": "https://files.form.io/pdf/5692b91fd1028f01000407e3/file/1ec0f8ee-6685-5d98-a847-26f67b67d6f0"
        }
    },
    "components": [
        {
            "title": "Page 1",
            "label": "Page 1",
            "type": "panel",
            "key": "page1",
            "components": [
                {
                    "label": "Gender",
                    "widget": "choicesjs",
                    "tableView": true,
                    "dataSrc": "custom",
                    "data": {
                        "custom": "values = this.getGenderValues()"
                    },
                    "valueProperty": "GenderId",
                    "template": "<span>{{ item.text }}</span>",
                    "selectThreshold": 0.3,
                    "key": "client.genderId",
                    "type": "select",
                    "indexeddb": {
                        "filter": {}
                    },
                    "input": true
                }
            ],
            "input": false,
            "tableView": false
        }
    ]
 };

getGenderValues = function() {
  var valuesArray = [];
  valuesArray.push({
    "text": "Male",
    "GenderId": "1",
    "id": "1"
  });
  valuesArray.push({
    "text": "Female",
    "GenderId": "2",
    "id": "2"
  });

  return valuesArray;
}

}

I have amended the angular demo version and added my code in Wizard component, showing above problem. The link is given below.

wizard.component.ts wizard.component.html

If you download and run the above repository, and click on Wizard menu, you will get the error: utils.js:337 An error occured within custom function for client.genderId TypeError: this.getGenderValues is not a function

travist commented 3 years ago

You cannot use this in custom execution code. Try to use instance instead, like instance.getGenderValues()

learner291 commented 3 years ago

I have tried the below code but still receiving the error, please see below the code and the error.

"data": {
              "custom": "values = instance.getGenderValues()"
        },

I have also tried below code but to no avail.

"data": {
              "custom": "{{ values = instance.getGenderValues() }}"
        },

image

learner291 commented 3 years ago

@travist I have managed to call the method using ngZone, however, I would appreciate if you could tell me if there is easier way as in javascript version I only needed to use values = this.getGenderValues()

I have added the below code in the constructor.

window['angularComponent'] = {
            componenet: this,
            zone: this._ngZone,
            getGenderValues: () => this.getGenderValues()
        };

I then changed the schema to below.

"custom": "values = window.angularComponent.getGenderValues();"

It is now calling the method. However, I am getting the data from the server so getGenderValues() is called asynchronisly and the returned values are empty until the result is returned from the server. I am returning local variable from getGenderValues()function so it does not populate the values in the drop down. If I use global variable and return it from getGenderValues() then the select drop down is populated. So, is there similar to await functionality that I can use in "custom": "values = window.angularComponent.getGenderValues();" so that it waits until the result is returned?

Many thanks for your help.

travist commented 3 years ago

You could add a global method to the evalOptions like so.

<formio [renderOptions]="{
  evalOptions: {
    customMethod: customMethod
  }
}"></formio>

Then, in your component, you would have the following.

class MyComponent {
  customMethod() {
    return '';
  }
}
learner291 commented 3 years ago

I am afraid still no luck after trying your suggestion. Please see below my code.

<formio [form]="myForm" [renderOptions]="{
  evalOptions: {
    getCars: getCars
  }
}"></formio>

My component

import { Component, AfterViewInit, NgZone, Inject } from '@angular/core';
import { FormioAppConfig } from '@formio/angular';
import { resolve } from 'path';
import { PrismService } from '../../Prism.service';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';

@Component({
  selector: 'app-wizard',
  templateUrl: './wizard.component.html',
  styleUrls: ['./wizard.component.scss']
})
export class WizardComponent implements AfterViewInit {
  private http: HttpClient;
  constructor(
    @Inject(HttpClient) http: HttpClient,
    public config: FormioAppConfig,
    public prism: PrismService,
    private _ngZone: NgZone

  ) {
    this.http = http;    
    //window['angularComponent'] = {
    //  componenet: this,
    //  zone: this._ngZone,
    //  getCars: () => this.getCars()
    //};
  }
  myForm = {
    "display": "wizard",
    "settings": {
      "pdf": {
        "id": "1ec0f8ee-6685-5d98-a847-26f67b67d6f0",
        "src": "https://files.form.io/pdf/5692b91fd1028f01000407e3/file/1ec0f8ee-6685-5d98-a847-26f67b67d6f0"
      }
    },
    "components": [
      {
        "title": "Page 1",
        "label": "Page 1",
        "type": "panel",
        "key": "page1",
        "components": [          
          {
            "widget": "choicesjs",
            "type": 'select',
            "tableView": true,
            "label": 'Model',
            "key": 'model',
            "placeholder": 'Select your model',            
            "dataSrc": "custom",
            "data": {
              "custom": "values = getCars();"
            },
          "valueProperty": 'Model_Name',
            "template": '<span>{{ item.Model_Name }}</span>',
            "selectValues": 'Results'
          }
        ],
        "input": false,
        "tableView": false
      }
    ]
  };

  ngAfterViewInit() {
    this.prism.init();
  }

  public getCars(){        
        var valuesArray: any = [];
        let url_ = "https://vpic.nhtsa.dot.gov/api/vehicles/getmodelsformake/honda?format=json";

        return this.http
          .get(`${url_}`).toPromise().then(function (data) {
            console.log(data);            
            valuesArray = data;
            return valuesArray;
          });
    }
}

I get the below error.

image

I then added the below code in the constructor of my component

window['angularComponent'] = {
      componenet: this,
      zone: this._ngZone,
      getCars: () => this.getCars()
    };

AND changed the json schema to below, and error disappears but the drop down values are not being populated. I think it could be due to promise being returned and applied to the local variable and for some reason not getting picked up by the select commponent.

            "data": {
              "custom": "values = window.angularComponent.getCars();"
            },

As you can see below, the data is returned by the url.

image

image

travist commented 3 years ago

This would probably require some developer support if you want the Form.io team to dive in and investigate why it isn't registering correctly. Maybe someone from the community could help out?

learner291 commented 3 years ago

What would be the cost and would that only be paid if developer is able to fix it?

travist commented 3 years ago

Please contact support@form.io and we will discuss this offline.

learner291 commented 3 years ago

I was able to resolve this issue by using below steps. I don't know if this is the correct way or not but it is now populating data.

Declare following in your constructor.

  constructor(
    public config: FormioAppConfig,
    public prism: PrismService
  )
  {
    window['angularComponent'] = {
      getGenderValues: () => this.getGenderValues()
    };
  }

Your custom data source in schema should be as per below.

"dataSrc": "custom",
            "data": {              
              "custom": "values = angularComponent.getGenderValues() "
            },

Your getGenderValues() function should be as per below.

Please note that you need to declare your variable genderValues = []; as global. It will work local if you are hardwiring genderValues but if you are getting values from a web service then it must be declare as global. I don't know why local will not work with web service.

  genderValues = [];
  getGenderValues() {
    this.genderValues = [];
    this.genderValues.push({
      "text": "Male",
      "GenderId": "1",
      "id": "1"
    });
    this.genderValues.push({
      "text": "Female",
      "GenderId": "2",
      "id": "2"
    });
    return this.genderValues; 
  }

I hope this would help someone.

learner291 commented 3 years ago

Although I have resolved the issue regarding displaying values in select box, I am encountering another issue relating to populating selected value. I have raised a separate issue: #745.