From fbaa35ba1ca606e00ed99bc58de0ac6c441e8266 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Mon, 18 Mar 2024 17:31:23 +0100
Subject: [PATCH 1/8] perf: get rid of unnecessary jquery
---
frappe/public/js/frappe/list/list_view.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index f20e2d351a..18e4ecf777 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -936,10 +936,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 = $(``);
- $(comment_count).append(`
+ comment_count = ``;
}
html += `
@@ -948,7 +948,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
${settings_button || assigned_to}
${modified}
- ${comment_count ? $(comment_count).prop("outerHTML") : ""}
+ ${comment_count || ""}
${comment_count ? 'ยท' : ""}
${this.get_like_html(doc)}
From b24062ea507505d5c3640b63755d862d82be0c66 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Mon, 18 Mar 2024 17:33:12 +0100
Subject: [PATCH 2/8] perf: construct html from scratch
Instead of parsing from string and re-querying the needed elements.
---
frappe/public/js/frappe/list/list_view.js | 31 ++++++++++++++---------
1 file changed, 19 insertions(+), 12 deletions(-)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 18e4ecf777..e1380e53e7 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -1047,23 +1047,30 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
const seen = this.get_seen_class(doc);
const div = document.createElement("div");
- div.innerHTML = `
-
-
-
-
-
-
- `;
+ const selectLikeSpan = document.createElement("span");
+ selectLikeSpan.classList.add("level-item", "select-like");
- const checkbox = div.querySelector(".list-row-checkbox");
- checkbox.dataset.doctype = this.doctype;
- checkbox.dataset.name = doc.name;
+ const checkboxInput = document.createElement("input");
+ checkboxInput.classList.add("list-row-checkbox");
+ checkboxInput.type = "checkbox";
+ checkboxInput.dataset.doctype = this.doctype;
+ checkboxInput.dataset.name = doc.name;
+ selectLikeSpan.appendChild(checkboxInput);
- const link = div.querySelector(".level-item a");
+ div.appendChild(selectLikeSpan);
+
+ const ellipsisSpan = document.createElement("span");
+ ellipsisSpan.classList.add("level-item", seen, "ellipsis");
+
+ const link = document.createElement("a");
+ link.classList.add("ellipsis");
link.dataset.doctype = this.doctype;
link.dataset.name = doc.name;
link.href = this.get_form_link(doc);
+
+ ellipsisSpan.appendChild(link);
+ div.appendChild(ellipsisSpan);
+
// "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);
From 69f634da0ad21d37de1b61d18069dbd843f5c20b Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Mon, 18 Mar 2024 18:27:13 +0100
Subject: [PATCH 3/8] perf: strip html from html fields only
---
frappe/public/js/frappe/list/list_view.js | 11 ++++++++---
frappe/public/js/frappe/model/model.js | 10 ++++++++++
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index e1380e53e7..e480eef35c 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -1071,9 +1071,14 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
ellipsisSpan.appendChild(link);
div.appendChild(ellipsisSpan);
- // "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);
+ let textValue;
+ if (frappe.model.html_fieldtypes.includes(subject_field.fieldtype)) {
+ // NOTE: this is very slow, so only do it for HTML fields
+ textValue = frappe.utils.html2text(value);
+ } else {
+ textValue = value;
+ }
+
link.title = textValue;
link.textContent = textValue;
diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js
index 5f5d534813..60270cace0 100644
--- a/frappe/public/js/frappe/model/model.js
+++ b/frappe/public/js/frappe/model/model.js
@@ -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" },
From 84e4fb4b03181a6cedb03bbf91d4c5b70aeeb501 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Mon, 18 Mar 2024 20:01:27 +0100
Subject: [PATCH 4/8] fix: handle empty `seen`
---
frappe/public/js/frappe/list/list_view.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index e480eef35c..55bd801cf2 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -1060,7 +1060,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
div.appendChild(selectLikeSpan);
const ellipsisSpan = document.createElement("span");
- ellipsisSpan.classList.add("level-item", seen, "ellipsis");
+ if (seen) {
+ ellipsisSpan.classList.add("level-item", seen, "ellipsis");
+ }
const link = document.createElement("a");
link.classList.add("ellipsis");
From d238d8b3fdf639f6aa17f59cb060012018dd7d0e Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Mon, 18 Mar 2024 20:02:14 +0100
Subject: [PATCH 5/8] perf: avoid useless JSON parsing
---
frappe/public/js/frappe/list/list_view.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 55bd801cf2..c15fbce386 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -1006,11 +1006,12 @@ 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 liked_by = doc._liked_by ? JSON.parse(doc._liked_by) : [];
const heart_class = liked_by.includes(frappe.session.user)
? "liked-by liked"
: "not-liked";
From de0cdc86baaf5f7595445950b8899eefdc10644f Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Mon, 18 Mar 2024 20:50:06 +0100
Subject: [PATCH 6/8] perf: get_like_html
---
frappe/public/js/frappe/list/list_view.js | 28 +++++++++++++++--------
1 file changed, 19 insertions(+), 9 deletions(-)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index c15fbce386..9eb2646637 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -1018,19 +1018,29 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
const title = liked_by.map((u) => frappe.user_info(u).fullname).join(", ");
const div = document.createElement("div");
- div.innerHTML = `
-
- ${frappe.utils.icon("es-solid-heart", "sm", "like-icon")}
-
- ${liked_by.length}
- `;
+ const like = document.createElement("span");
+ like.classList.add("like-action", heart_class);
+ const like_icon = document.createElement("svg");
+ like_icon.classList.add("es-icon", "es-solid", "icon-sm", "like-icon");
- const like = div.querySelector(".like-action");
+ const use = document.createElement("use");
+ use.setAttribute("href", "#es-solid-heart");
+ use.classList.add("like-icon");
+ like_icon.appendChild(use);
+
+ like.appendChild(like_icon);
+ like.dataset.doctype = this.doctype;
+ like.dataset.name = doc.name;
like.setAttribute("data-liked-by", doc._liked_by || "[]");
- like.setAttribute("data-doctype", this.doctype);
- like.setAttribute("data-name", doc.name);
like.setAttribute("title", title);
+ const likes_count = document.createElement("span");
+ likes_count.classList.add("likes-count");
+ likes_count.textContent = liked_by.length;
+
+ div.appendChild(like);
+ div.appendChild(likes_count);
+
return div.innerHTML;
}
From 3dd648e5f817b53bab066cec3a922525df5bb805 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Tue, 19 Mar 2024 01:13:56 +0100
Subject: [PATCH 7/8] refactor: use ElementFactory
---
frappe/public/js/frappe/list/list_view.js | 165 ++++++++++++++--------
1 file changed, 107 insertions(+), 58 deletions(-)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 9eb2646637..844b809c65 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -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() {
@@ -875,7 +876,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];
}
@@ -1011,28 +1012,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
get_like_html(doc) {
+ const ef = this._element_factory;
const liked_by = doc._liked_by ? JSON.parse(doc._liked_by) : [];
- const heart_class = liked_by.includes(frappe.session.user)
- ? "liked-by liked"
- : "not-liked";
+ 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");
- const like = document.createElement("span");
- like.classList.add("like-action", heart_class);
- const like_icon = document.createElement("svg");
- like_icon.classList.add("es-icon", "es-solid", "icon-sm", "like-icon");
-
- const use = document.createElement("use");
- use.setAttribute("href", "#es-solid-heart");
- use.classList.add("like-icon");
- like_icon.appendChild(use);
-
- like.appendChild(like_icon);
- like.dataset.doctype = this.doctype;
- like.dataset.name = doc.name;
- like.setAttribute("data-liked-by", doc._liked_by || "[]");
- like.setAttribute("title", title);
+ const like = ef.get_like_element(doc.name, is_liked, liked_by, title);
const likes_count = document.createElement("span");
likes_count.classList.add("likes-count");
@@ -1044,58 +1030,43 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
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");
- const selectLikeSpan = document.createElement("span");
- selectLikeSpan.classList.add("level-item", "select-like");
-
- const checkboxInput = document.createElement("input");
- checkboxInput.classList.add("list-row-checkbox");
- checkboxInput.type = "checkbox";
- checkboxInput.dataset.doctype = this.doctype;
- checkboxInput.dataset.name = doc.name;
- selectLikeSpan.appendChild(checkboxInput);
-
- div.appendChild(selectLikeSpan);
-
- const ellipsisSpan = document.createElement("span");
- if (seen) {
- ellipsisSpan.classList.add("level-item", seen, "ellipsis");
- }
-
- const link = document.createElement("a");
- link.classList.add("ellipsis");
- link.dataset.doctype = this.doctype;
- link.dataset.name = doc.name;
- link.href = this.get_form_link(doc);
-
- ellipsisSpan.appendChild(link);
- div.appendChild(ellipsisSpan);
-
- let textValue;
if (frappe.model.html_fieldtypes.includes(subject_field.fieldtype)) {
// NOTE: this is very slow, so only do it for HTML fields
- textValue = frappe.utils.html2text(value);
+ return frappe.utils.html2text(value);
} else {
- textValue = value;
+ return value;
}
-
- link.title = textValue;
- link.textContent = textValue;
-
- return div.innerHTML;
}
get_indicator_html(doc, show_workflow_state) {
@@ -2164,3 +2135,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;
+ }
+}
From cd9f9ab473b6bfc6ad2f8fafa8a94aa0c0612b86 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Tue, 19 Mar 2024 19:17:16 +0100
Subject: [PATCH 8/8] refactor: get_like_html
---
frappe/public/js/frappe/list/list_view.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 2506c9e724..21814d2383 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -1012,15 +1012,14 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
get_like_html(doc) {
- const ef = this._element_factory;
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");
- const like = ef.get_like_element(doc.name, is_liked, liked_by, title);
-
- div.appendChild(like);
+ div.appendChild(
+ this._element_factory.get_like_element(doc.name, is_liked, liked_by, title)
+ );
return div.innerHTML;
}