Virtual DocType as Child Table

Hi there,

I am wondering if it’s possible to create a child table as a virtual doctype.

So technically it works: I mean, I can create the DocType with the Flags:
x Virtual
x Child Table

The generated .py file has the methods:

  • db_insert
  • db_update
  • get
  • get_list
  • get_count
  • get_stats

I expected to implement get_list() to return the values I want to display.

I just want to aggregate some other data in another block, which are already persisted in the database.
So I thought that might be the easiest way … but I cannot find anything about that in the docs or in the forum… so I hope somebody can help me.

The current result is →

The child table is embedded at the parent doctype but with empty entities.

I’ve called frappe.logger() within the method get_list(), which doesnt log anything.

So it seems, that there needs to be another method to be redefined to gather the Child Table’s data… hope anybody did this already?

Thanks alot!

This is a really interesting idea. Among other things, it suggests a way to get real table joins through the regular form interface. Last I check in v13, virtual doctypes were still only half implemented, but I’ve heard it’s working more fully in the v14 betas. What version are you testing on?

I have tried to link to Virtual Doctype but it throws a frappe.db.exists error on V13. I will try on V14

I am on V13 … it works without any error, but simply displays an empty table

After having other topics, I finally got back to that and solved it ugly via JS.

As I want to load the data asynchronously, only when the Section is expanded, I created an API method, let’s call it here ‘my_app.api.load_data’.

Then in the expand-event

frappe.call('my_app.api.load_data', {
      'sales_invoice': cur_frm.doc.name
      }).then(result => {
        for(let item_data of result.data) {
          const new_item = cur_frm.add_child('table_identifier');

          new_item.attr1 = item_data.val1;
          new_item.attr2 = item_data.val2;
          new_item.attr... 
        }

        cur_frm.refresh_field('table_identifier');
      });
      ...
})

Maybe it helps sbd else.

In the meantime I am hoping that I just didnt find a better way (and I get informed about it) :slight_smile:

2 Likes

smart workaround.

1 Like

how you hook to expand-event? sample code also? thanks.

Haha good point (I forgot), this was also a very dirty hack (as I am not 100% sure about the lifecycle of the frappe class instances).

In the refresh method of the Parent doctype:

refresh: function(doc, dt, dn) {
    frappe.model.clear_table(cur_frm.doc, 'table_identifier');
    cur_frm.refresh_field('table_identifier');
    
    if (cur_frm.fields_dict.table_identifier.collapse_link[0].classList.contains('collapsed')) {
      cur_frm.fields_dict.table_identifier.collapse_link[0].addEventListener('click', this.events.load_data, { once: true });
    } else {
      this.events.load_data();
    }
  },

The event’s method will then not be called in the “this” scope.
But I cannot use this.events.load_data.bind(this) as it would generate an anonymous function, which would then be bound additionally to the click, each time another refresh appears.

So my loading function doesn’t care about its scope, which made it ok.

Otherwise you could memoize the anonymous function to get the same effect, but as I previously mentioned, I don’t have enough experience with frappe to know about the instance lifecycles.

I didn’t find a “real” attribute for the current indication of collapse/expand, that’s why I triggered the DOM element’s class… again a kind of ugly.

If there a better ways for that, it would really be useful to have this in the docs.

Tested the python onload event, it also works

class TestVirtualChild(Document):

    def onload(self):

        self.append("child",

            {"item":"1234",

             "remark": "my remarks"})

Strange… you tested this without any JS, right?

It’s not called when I try this.

It neither aborts, when I just try to crash it… so on my v13 the onload method is just not called obviously.

def onload(self):
	frappe.throw('sdf')

Yes, I tested just using pure python code as above.

Very strange… but unfortunately I encountered further trouble when persisting/deleting the parent doctype.
Opened a PR for that… hope this will get reviewed soon, but for now I have to disable that feature because of that.