Changing fieldtype does nothing

Hi,

I’m developing an app using ERPNext v12.17

Right now I would like to change a Data field into a Select field for purposes of form entry. According to the documentation, I should use frm.set_df_property() to change the fieldtype (and options), and frappe should “refresh the field” as part of the function call.

Here’s what I tried:

frappe.ui.form.on('Test Doctype', {
    setup: (frm) => {
        frm.set_df_property('test_field', 'fieldtype', 'Select');
        frm.set_df_property('test_field', 'options', ['one', 'two']);
    },
    before_load: (frm) => {
        frm.set_df_property('test_field', 'fieldtype', 'Select');
        frm.set_df_property('test_field', 'options', ['one', 'two']);
    },
    onload: (frm) => {
        frm.set_df_property('test_field', 'fieldtype', 'Select');
        frm.set_df_property('test_field', 'options', ['one', 'two']);
    },
    refresh: (frm) => {
        frm.set_df_property('test_field', 'fieldtype', 'Select');
        frm.set_df_property('test_field', 'options', ['one', 'two']);
    },
    onload_post_render: (frm) => {
        frm.set_df_property('test_field', 'fieldtype', 'Select');
        frm.set_df_property('test_field', 'options', ['one', 'two']);
    },
});

After the code runs, the field remains a Data field, with no apparent change. Am I doing something wrong?

Looking through the frappe code base for instances of set_df_property, I don’t see any (apparent) lines of code to force a screen redraw, so I don’t understand why it doesn’t work for me.

NB: I’ve also tried running frm.refresh_field('test_field') after set_df_property, which also did not help.

1 Like

Hi @ebsjbulcher,
First of all you have to understand that frappe uses metadata to build its ui. The metadata saved in json format. Dynamically change the behaviour of the fieldtype is making your data inconsistent. So its better to change it directly in doctype of what kind of fieldtype you want to have for your field.

Hi @mrjurin, thanks for the reply!

frappe uses metadata to build its ui

I understand frappe uses metadata to build the UI; I’m trying to use the built-in API to alter the UI. I ran into numerous posts about people using set_df_property to change field types when I was researching my issue, and I’m trying to figure out why it doesn’t work for me?

Dynamically change the behaviour of the fieldtype is making your data inconsistent.

Why would changing the data type from Data to Select cause my data to be inconsistent? Both data types are stored as strings in the MariaDB backend.

So its better to change it directly in doctype of what kind of fieldtype you want to have for your field.

This doesn’t fit my use-case; allow me to explain

When entering in a new document, the field has a limited set of possible values. That set of values change every month. If I change the field type into a dropdown, then viewing old documents and hitting ‘save’ will overwrite the field with a value from the new set of options.

What I would like to do is offer my users a Select field if the document is a new form; otherwise, leave the field as a Data field, which is appropriate for the at-rest data. That will also make filtering in the list view work properly.

Hello
I have the same requirement as yours. So, did you find any solution?

I’m using a simple data field, so no.

I thought I would post this here in case it helps someone else.

Use Case:

  1. DocType Test1 field test is a Data field.
  2. I want test to see the values from DocType Test2 but also to be able to accept a different value.

Solution:

  1. Create a new field called temp_test that is defined as a link field but with the option ignore_link_validation set to true. This allows the user to enter any value into temp_test, not just the link field values. This is probably not necessary for @ebsjbulcher’s requirement because it appears he wants to restrict values when the document is new but not update the field after the document is created.
  2. Hide the original field test.
  3. Whenever temp_test is changed, set test to the value in temp_test.

For @ebsjbulcher’s original question, you could put an if statement on this to only change the field type when frm.is_new() otherwise leave it as Data.

BTW, my specific use case is for US based addresses, I want the state field to only accept values from a State DocType. Otherwise I want the user to be allowed to put whatever value they want into the field.

HTH.

frappe.ui.form.on("Test1", {
    setup: function(frm) {
        frm.temp_test = frappe.ui.form.make_control({
            parent: frm.fields_dict.test.wrapper,    // where your field will be rendered
            df: {
                  label: 'Test',
                  fieldname: 'temp_test',
                  fieldtype: 'Link',
                  options: "Test2",
                  only_select: 1,
                  onchange: function() {
                      frm.set_value("test", frm.temp_test.value);
                  },
            },
            render_input: true
        })
        frm.temp_test.df.ignore_link_validation = 1;  // Optional if you want to add any value to the field
        frm.temp_test.value = frm.doc.test;
        frm.fields_dict["temp_test"] = frm.temp_test;  // Add the new field to the `fields_dict`. This allows `refresh_field` to work
        frm.refresh_field("temp_test");

        //Hide the original field
        let test_wrapper = frm.fields_dict.test.$wrapper;  // Get the wrapper div
        let form_group = test_wrapper.find(".form-group");   // Get the divs with class `form-group`
        form_group[0].style.display = "none";   // Hide the first `form-group` div which holds the original field
        form_group[0].style.visibility = "hidden";
    },

    refresh: function(frm) {
        frm.temp_test.value = frm.fields_dict.test.value;
        frm.refresh_field("temp_test");
    }
});

You wont be able to change the Data Type of field dynamically in Frappe.

The best practice is to make the changes Directly in the DocType, but that won’t make dynamic.
So you can have HTML field, and then using Wrappers, create your desired field type, and save the value after validating to the original field.

Example:

let input_html = `<input type="text" id="custom_input" class="form-control" placeholder="Enter Data">`;
frm.fields_dict['html_field'].wrapper.innerHTML = input_html;

Or you can have duplicate field as per your use case, and toggle the display as per your logic, but the data being saved will be at the original field at the time of Save.

Hi @ebsjbulcher,

How can I change the field type in a child table?

yes you can just use the property setter tool no need for coding or script

@Mohammadali I never came up with a solution and have since moved on, although the new Autocomplete field might be an option. I would start there (I doubt the field functionality has changed enough to make a Select field work for my use-case). It doesn’t function quite the same a select list, but it is similar enough it might work for you.

Alternatively, if a Select list was a requirement for some reason, I might consider using a Data field in conjunction with a visible Select field and try to work out a solution.

@teharris1 I didn’t realize there was an option for ignore_link_validation. Your Link field sounds reasonably doable - even preferable for certain use cases. In my case that wasn’t what I wanted, and the additional latency and overhead would have been a big thumbs down.