Merge pull request #25524 from barredterra/list-performance
perf: render list
This commit is contained in:
commit
90c97b3c13
2 changed files with 129 additions and 50 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue