Developer suggestions for improved UI Grid?

That’s a link to the frappejs documentation. I suspect that everyone here is asking about regular frappe.

@aakvatech To clarify. My post was not intended to propose changes to ERPNext, or suggest we champion a future solution. I’m looking for practical workarounds for what we’ve got. Developers who have solved this problem already, by creating their own custom “widget”, and are willing to share how they accomplished that.

@auliabismar @peterg
Correct, talking about regular Frappe+ERPNext.

I would also like to see the Grid UI and Web Forms with grid UI work better because it helps in other frappe apps. Not able to see more than 11 columns and that too in different sizes brings in a limitation when users have wider screens and want to see all information at a go.

I believe the Web Forms were deprecated due to a need to have employee self service user type, but I stand to be corrected.

Following up on my previous post, here’s a trivial proof of concept that shows how incredibly powerful vue templates can be in the context of frappe forms.

Step 1. In any doctype form, create a new field of type HTML and use the following template in the “Options” box:

    <table id="cat-facts" class="table table-bordered">
        <caption style="caption-side: top;">Cat Facts</caption>
        <thead>
            <tr class="d-flex">
                <th class="col-3">ID</th>
                <th class="col-2">Date Created</th>
                <th class="col-7">Fact</th>
            </tr>
        </thead>
        <tbody>
            <tr v-if="facts_loading">
                <td style="text-align: center">
                    <div class="spinner-border spinner-border-sm text-secondary" role="status">
                        <span class="sr-only">Loading...</span>
                    </div>
                </td>
            </tr>
            <tr v-cloak class="d-flex" v-for="(fact, index) in cat_facts">
                <td class="col-3">{{ fact._id }}</td>
                <td class="col-2">{{ fact.createdAt.slice(0,10) }}</td>
                <td class="col-7">{{ fact.text }}</td>
            </tr>
            <tr v-cloak v-if="cat_facts.length === 0 && !facts_loading">
                <td style="font-style: italic">No data.</td>
            </tr>
        </tbody>
    </table>

(This is quite a bit longer than it needs to be because it has a few niceties added, such as a spinning wheel during load and a “No data.” row that appears if the table is empty.)

Step 2. Create a new client script for whatever doctype you’re using, and add this form javascript:

frappe.ui.form.on('Offer Term', {
	refresh(frm) {
		var vm = new Vue({ 
            el: "#cat-facts", 
            data: {
                facts_loading: true,
                cat_facts: []
            }
        });
        frm.vm = vm;
        
        $.get("https://cat-fact.herokuapp.com/facts/random?animal_type=cat&amount=5", (data) => {
            vm.facts_loading = false;
            vm.cat_facts = data;
        })
	}
})

Step 3. Load the doctype up and behold your creation:

It’s all pretty simple to set up. I haven’t used the component library @youssef mentioned, but it looks great and I suspect it’d be pretty straight forward to incorporate. If you need something more powerful than the built in widget library, Vue gives you almost limitless power.

5 Likes

@peterg
Although I’m unable to repeat your demo (I’m probably missing prerequisite Vue components), the screenshot looks very cool!

Conceptually, I believe I understand what’s happening:

  • A Vue element is associated with the HTML table having id = “cat-facts”
  • On refresh, data is loaded into an Object from https://cat-fact.herokuapp.com.
    • Which also flips the boolean variable facts_loading to false, thus stopping the spinner.
  • v-for is doing a loop (similar to a Jinja/Mustache/Handlebars template language)
    • 1 row displayed per record in the ‘cat_facts’ array/list.
  • CSS to make columns the appropriate width, etc.

Unfortunately, Frontend development is currently my weakest area. The type of Frontend wizardy you just demonstrated is approaching the limits of my know-how. If I tried to take your example, and “turn it up to 11”, I would quickly get lost. For example:

  • Fetching data from DocFields, without making a new HTTP call. I’m mostly ignorant about how Doc data is being stored in the browser. I’m assuming it’s either inside some JS objects. Or there’s a way to walk the DOM and get it. Or both. :man_shrugging:
  • Adding anchors intelligently based on metadata.
  • Ability to “expand” into the full-screen details page.
  • Sort/Filter.
  • Etc.

For a few years, I’ve been meaning to learn Vue, but always get caught up in other priorities. Maybe I’ll carve out some time this winter, and finally dive into it.

For me, it’s quickly becoming a must-have skill. The out-of-the-box Frappe frontend isn’t meeting my clients’ requirements (or my own). So need new options.

Hmm…I wonder what’s missing. It should all work straight out of the box. Vue comes preinstalled in vanilla Frappe. Did you tick the “enabled?” box on the client script? I always forget to do that. Any errors in the console? Can you test that the client script is running by putting a console.log("script loaded") in the refresh event?

Your account is spot on. Frappe uses bootstrap, so there’s no need for custom css beyond that. If you notice the class="col-3" lines, those are specifying the width, etc.

Vue is a binding framework (among other things). Back in the bad old days, you had to muck around a lot with event listeners and update functions to keep the data and the presentation in sync, but Vue allows you to bind javascript variables to the DOM directly. Once you get the demo up and running, try typing cur_frm.vm.cat_facts into the console. That’s your data structure, and if you change any of the values it will immediately be reflected in the data table. (cur_frm is the standard frappe variable representing the currently active form; vm is the vue instance I created; and cat_facts is the data structure being represented in the template.)

There are a number of other frameworks similar to Vue (React, Angular, etc.), but one thing that’s nice about Vue is that it can be adopted incrementally. You can load it up and use it for just one field or function, which is ideal for use in the context of an existing framework like Frappe. Other frameworks tend to be more all or nothing.

This example is pretty trivial, but it should be possible to create a two-way binding directly to the cur_frm.doc variable, which represents all document variables. There’s no need to troll the DOM or make additional HTTP calls. For a fullscreen details page, you could use either the frappe or the boostrap modals. Filtering/sorting is all just done directly on the javascript data, which will update the view immediately. Lots of possibilities, and Vue has an extremely robust component library.

Let me know if you still can’t get the demo working after trying the steps I mentioned above. Happy to troubleshoot, and happy to walk through more details with anyone interested!

4 Likes

I managed make it work

the problem I encountered is that the template text as html field’s options saved to database get sanitized, the Vue attribute v-if get auto removed, so the template after save is as following


finally there is error in web console

I manually set the options via bench console to solve the issue.

so if we got to integrate Vue, we need to adjust the sanitize_html function to support Vue attribute.

2 Likes

You can use Vanilla JavaScript with JQuery, check this example :

Of course, the code in the previous example needs to be modified to fit your use case, and the HTML field must be used to create a place in the form to render it in, but the principle at the end, is the same.

But to create a child table with features similar to those in frappe, will take more than that, but it is possible.

Thanks for all your work @szufisher. The html sanitation is strange, though it explains the console error you’re seeing. I wonder why it’s happening to you and not to me. Hmm…I’ll poke around to see if I can figure out what’s happening. Do you know what version you’re testing on?

I think the grid should be updated to be more
like a Notion.so database table! Add some colour tags over texts, easy edit mode (edit from
the grid), drop downs, etc.

I had the same problem and messages as @szufisher.

@youssef , thanks for sharing that tutorial!
Really fantastic; there’s a lot of things to learn from that. :100:

1 Like

After 15min of poking around, I can’t figure out why your HTML is getting sanitized by mine isn’t. I think I might have found a fix, though. If you look at your field in the Custom Field list (not the Customize Form page), the new field you created should have an option called “Ignore XSS Filter”. If that’s ticked, Frappe should leave all of the vue-specific attributes in place. If you try it, let us know if it works.

to make the ignore_xss_filter work, the following method in BaseDocument need to be changed(patched)

1 Like

This is refactoring to your code
we should have a custom field with name “html_items”

frappe.ui.form.on('Sales Invoice', {
  onload: frm => {
      set_html(frm);
      update_data(frm);
  }
});

const set_html = frm => {
  $("#html_items").remove();
  $(`
      <table id="items" class="table table-bordered">
          <caption style="caption-side: top;">HTML Items</caption>
          <thead>
              <tr class="d-flex">
                  <th class="col-4">Item</th>
                  <th class="col-2">Quantity</th>
                  <th class="col-3">Rate</th>
                  <th class="col-3">Amount</th>
              </tr>
          </thead>
          <tbody>
              <tr v-cloak class="d-flex" v-for="(item, index) in items">
                  <td class="col-4">{{ item.item_code }}</td>
                  <td class="col-2">{{ item.qty }}</td>
                  <td class="col-3">{{ fmt_money( item.rate, currency=cur_frm.doc.currency)}}</td>
                  <td class="col-3">{{ fmt_money( item.amount, currency=cur_frm.doc.currency)}}</td>
              </tr>
              <tr v-cloak v-if="items.length === 0 ">
                  <td style="font-style: italic">No data.</td>
              </tr>
          </tbody>
      </table>
  `).appendTo(frm.fields_dict.html_items.wrapper);
};

const update_data = frm => {
  const vm = new Vue({
      el: "#items",
      data: {
          items: frm.doc.items
      }
  });
};

8 Likes

For me, Improved Grid should have :

  • Overcome limitation of 11 columns
  • Columns can be re-sized
  • Column’s order can be re-arranged
  • Show(Add)/Hide columns
  • All above changes in saved in user profile
  • Search functionality in Child Table
  • Tooltip of truncated cell’s value

Regards,
Subhajit

5 Likes

Just a quick update: I recently upgraded my system to 13.7, and as soon as I did I started encountering the same problems others described with Vue. It seems the HTML sanitization started happening somewhere between 13.4 and 13.7.

Adding the template from javascript, as @youssef suggests, works great, and in general I think that’s a more robust approach. By keeping it all in javascript, there are better options for error handling, etc.

1 Like

waiting to see updated complete tutorial on latest version 13 and version 14.

1 Like

It all works the same on v14, as far as I can tell!

I think Frappe Datatable is the better solution for this usecase.