Merge pull request #25524 from barredterra/list-performance

perf: render list
This commit is contained in:
Ankush Menat 2024-03-27 10:56:12 +05:30 committed by GitHub
commit 90c97b3c13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 129 additions and 50 deletions

View file

@ -29,6 +29,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
2000
);
this.count_upper_bound = 1001;
this._element_factory = new ElementFactory(this.doctype);
}
has_permissions() {
@ -876,7 +877,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
column_html = this.settings.formatters[fieldname](value, df, doc);
} else {
column_html = {
Subject: this.get_subject_html(doc),
Subject: this.get_subject_element(doc).innerHTML,
Field: field_html(),
}[col.type];
}
@ -937,10 +938,10 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
let comment_count = null;
if (this.list_view_settings && !this.list_view_settings.disable_comment_count) {
comment_count = $(`<span class="comment-count d-flex align-items-center"></span>`);
$(comment_count).append(`
comment_count = `<span class="comment-count d-flex align-items-center">
${frappe.utils.icon("es-line-chat-alt")}
${doc._comment_count > 99 ? "99+" : doc._comment_count || 0}`);
${doc._comment_count > 99 ? "99+" : doc._comment_count || 0}
</span>`;
}
html += `
@ -949,7 +950,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
${settings_button || assigned_to}
</div>
<span class="modified">${modified}</span>
${comment_count ? $(comment_count).prop("outerHTML") : ""}
${comment_count || ""}
${comment_count ? '<span class="mx-2">·</span>' : ""}
<span class="list-row-like hidden-xs style="margin-bottom: 1px;">
${this.get_like_html(doc)}
@ -1007,70 +1008,60 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
get_seen_class(doc) {
return JSON.parse(doc._seen || "[]").includes(frappe.session.user) ? "" : "bold";
const seen_by = doc._seen ? JSON.parse(doc._seen) : [];
return seen_by.includes(frappe.session.user) ? "" : "bold";
}
get_like_html(doc) {
const liked_by = JSON.parse(doc._liked_by || "[]");
const heart_class = liked_by.includes(frappe.session.user)
? "liked-by liked"
: "not-liked";
const liked_by = doc._liked_by ? JSON.parse(doc._liked_by) : [];
const is_liked = liked_by.includes(frappe.session.user);
const title = liked_by.map((u) => frappe.user_info(u).fullname).join(", ");
const div = document.createElement("div");
div.innerHTML = `
<span class="like-action ${heart_class}">
${frappe.utils.icon("es-solid-heart", "sm", "like-icon")}
</span>
`;
const like = div.querySelector(".like-action");
like.setAttribute("data-liked-by", doc._liked_by || "[]");
like.setAttribute("data-doctype", this.doctype);
like.setAttribute("data-name", doc.name);
like.setAttribute("title", title);
div.appendChild(
this._element_factory.get_like_element(doc.name, is_liked, liked_by, title)
);
return div.innerHTML;
}
get_subject_html(doc) {
let subject_field = this.columns[0].df;
get_subject_element(doc) {
const ef = this._element_factory;
const div = document.createElement("div");
const checkboxspan = ef.get_checkboxspan_element();
const ellipsisSpan = document.createElement("span");
const seen = this.get_seen_class(doc);
if (seen) {
ellipsisSpan.classList.add("level-item", seen, "ellipsis");
}
div.appendChild(checkboxspan).appendChild(ef.get_checkbox_element(doc.name));
div.appendChild(ellipsisSpan).appendChild(
ef.get_link_element(doc.name, this.get_form_link(doc), this.get_subject_text(doc))
);
return div;
}
get_subject_text(doc) {
const subject_field = this.columns[0].df;
let value = doc[subject_field.fieldname];
if (this.settings.formatters && this.settings.formatters[subject_field.fieldname]) {
let formatter = this.settings.formatters[subject_field.fieldname];
value = formatter(value, subject_field, doc);
}
if (!value) {
value = doc.name;
}
const seen = this.get_seen_class(doc);
const div = document.createElement("div");
div.innerHTML = `
<span class="level-item select-like">
<input class="list-row-checkbox" type="checkbox">
</span>
<span class="level-item ${seen} ellipsis">
<a class="ellipsis"></a>
</span>
`;
const checkbox = div.querySelector(".list-row-checkbox");
checkbox.dataset.doctype = this.doctype;
checkbox.dataset.name = doc.name;
const link = div.querySelector(".level-item a");
link.dataset.doctype = this.doctype;
link.dataset.name = doc.name;
link.href = this.get_form_link(doc);
// "Text Editor" and some other fieldtypes can have html tags in them so strip and show text.
// If no text is found show "No Text Found in {Field Label}"
let textValue = frappe.utils.html2text(value);
link.title = textValue;
link.textContent = textValue;
return div.innerHTML;
if (frappe.model.html_fieldtypes.includes(subject_field.fieldtype)) {
// NOTE: this is very slow, so only do it for HTML fields
return frappe.utils.html2text(value);
} else {
return value;
}
}
get_indicator_html(doc, show_workflow_state) {
@ -2146,3 +2137,81 @@ frappe.get_list_view = (doctype) => {
let route = `List/${doctype}/List`;
return frappe.views.list_view[route];
};
class ElementFactory {
/* Pre-create templates for HTML Elements on initialization and provide them
via the get_xxx_element methods. */
constructor(doctype) {
this.templates = {
checkbox: this.create_checkbox_element(doctype),
checkboxspan: this.create_checkboxspan_element(),
link: this.create_link_element(doctype),
like: this.create_like_element(doctype),
};
}
create_checkbox_element(doctype) {
const checkbox = document.createElement("input");
checkbox.classList.add("list-row-checkbox");
checkbox.type = "checkbox";
checkbox.dataset.doctype = doctype;
return checkbox;
}
create_link_element(doctype) {
const link = document.createElement("a");
link.classList.add("ellipsis");
link.dataset.doctype = doctype;
return link;
}
create_checkboxspan_element() {
const checkboxspan = document.createElement("span");
checkboxspan.classList.add("level-item", "select-like");
return checkboxspan;
}
create_like_element(doctype) {
const like = document.createElement("span");
like.classList.add("like-action");
like.innerHTML = frappe.utils.icon("es-solid-heart", "sm", "like-icon");
like.dataset.doctype = doctype;
return like;
}
get_checkbox_element(name) {
const checkbox = this.templates.checkbox.cloneNode(true);
checkbox.dataset.name = name;
return checkbox;
}
get_checkboxspan_element() {
return this.templates.checkboxspan.cloneNode(true);
}
get_link_element(name, href, text) {
const link = this.templates.link.cloneNode(true);
link.dataset.name = name;
link.href = href;
link.title = text;
link.textContent = text;
return link;
}
get_like_element(name, liked, liked_by, title) {
const like = this.templates.like.cloneNode(true);
like.dataset.name = name;
const heart_classes = liked ? ["liked-by", "liked"] : ["not-liked"];
like.classList.add(...heart_classes);
like.setAttribute("data-liked-by", liked_by || "[]");
like.setAttribute("title", title);
return like;
}
}

View file

@ -108,6 +108,16 @@ $.extend(frappe.model, {
"docstatus",
],
html_fieldtypes: [
"Text Editor",
"Text",
"Small Text",
"Long Text",
"HTML Editor",
"Markdown Editor",
"Code",
],
std_fields: [
{ fieldname: "name", fieldtype: "Link", label: __("ID") },
{ fieldname: "owner", fieldtype: "Link", label: __("Created By"), options: "User" },