class CustomControlAttach extends frappe.ui.form.ControlAttach {
clear_attachment() {
let me = this;
if (this.frm) {
// Remove the file from the form and refresh
me.parse_validate_and_set_in_model(null);
me.refresh();
me.frm.doc.docstatus == 1 ? me.frm.save("Update") : me.frm.save();
} else {
// Handle the case where there's no form context
this.dataurl = null;
this.fileobj = null;
this.set_input(null);
this.parse_validate_and_set_in_model(null);
this.refresh();
}
}
}
Here i want to change the default functionality of Attach button by removing remove attach functionality how can i do that?
that custom class file added in hooks in app_include_js
but standard code running why?
it is not a doctype class it is a javascript class?
NCP
July 22, 2024, 8:44am
4
You have to check the example of hrms:
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.provide("hrms.hr");
hrms.hr.AttendanceControlPanel = class AttendanceControlPanel extends frappe.ui.form.Controller {
onload() {
this.frm.set_value("att_fr_date", frappe.datetime.get_today());
this.frm.set_value("att_to_date", frappe.datetime.get_today());
}
refresh() {
this.frm.disable_save();
this.show_upload();
this.setup_import_progress();
}
custom js file
hooks.py
I added like this but not working it is correct?
NCP
July 22, 2024, 9:24am
6
No.
Again check the example of india-compliance app.
2 Likes
How to override js class in frappe>
Created one file attach.js in custom app>
// Define the custom class extending frappe.ui.form.ControlAttach
class CustomControlAttach extends frappe.ui.form.ControlAttach {
// Override the clear_attachment method
clear_attachment() {
console.log("Custom clear_attachment method is loaded and executed"); // Add this line
let me = this;
if (this.frm) {
// Remove the file from the form and refresh
me.parse_validate_and_set_in_model(null);
me.refresh();
me.frm.doc.docstatus == 1 ? me.frm.save("Update") : me.frm.save();
} else {
// Handle the case where there's no form context
this.dataurl = null;
this.fileobj = null;
this.set_input(null);
this.parse_validate_and_set_in_model(null);
this.refresh();
}
}
}
// Ensure to use your custom class in the form instead of the default ControlAttach
frappe.ui.form.ControlAttach = CustomControlAttach;
Created project_name.bundle.js file >
import "./attach.js";
Added in Hooks >
app_include_js = [
"project_name.bundle.js"
]
Thanks @NCP for guidance
3 Likes
hyaray
October 24, 2024, 12:23pm
8
these class are defined to frapps
namespace, It works.
My question is: how to override class defined by export default class XXX
?
EX: class export default class GridRow
in grid_row.js
.
saloni
January 2, 2025, 6:30am
9
I did same My app name is company
1 create file attach.js
file path -->apps/company/company/public/js/attach.js
frappe.ui.form.ControlAttach = class ControlAttach extends frappe.ui.form.ControlData {
make_input() {
let me = this;
this.$input = $('<button class="btn btn-default btn-sm btn-attach">')
.html(__("Attach"))
.prependTo(me.input_area)
.on({
click: function () {
me.on_attach_click();
},
attach_doc_image: function () {
me.on_attach_doc_image();
},
});
this.$value = $(
`<div class="attached-file flex justify-between align-center">
<div class="ellipsis">
${frappe.utils.icon("es-line-link", "sm")}
<a class="attached-file-link" target="_blank"></a>
</div>
<div>
<a class="btn btn-xs btn-default" data-action="reload_attachment">${__("Reload File")}</a>
<a class="btn btn-xs btn-default" data-action="clear_attachment">${__("Clear")}</a>
</div>
</div>`
)
.prependTo(me.input_area)
.toggle(false);
this.input = this.$input.get(0);
this.set_input_attributes();
this.has_input = true;
frappe.utils.bind_actions_with_object(this.$value, this);
this.toggle_reload_button();
}
clear_attachment() {
let me = this;
if (this.frm) {
me.parse_validate_and_set_in_model(null);
me.refresh();
me.frm.attachments.remove_attachment_by_filename(me.value, async () => {
await me.parse_validate_and_set_in_model(null);
me.refresh();
// me.frm.doc.docstatus == 1 ? me.frm.save("Update") : me.frm.save();
});
} else {
this.dataurl = null;
this.fileobj = null;
this.set_input(null);
this.parse_validate_and_set_in_model(null);
this.refresh();
}
}
reload_attachment() {
if (this.file_uploader) {
this.file_uploader.uploader.upload_files();
}
}
on_attach_click() {
this.set_upload_options();
this.file_uploader = new frappe.ui.FileUploader(this.upload_options);
}
on_attach_doc_image() {
this.set_upload_options();
this.upload_options.restrictions.allowed_file_types = ["image/*"];
this.file_uploader = new frappe.ui.FileUploader(this.upload_options);
}
set_upload_options() {
let options = {
allow_multiple: false,
on_success: (file) => {
this.on_upload_complete(file);
this.toggle_reload_button();
},
restrictions: {},
};
if (this.frm) {
options.doctype = this.frm.doctype;
options.docname = this.frm.docname;
options.fieldname = this.df.fieldname;
options.make_attachments_public = this.df.make_attachment_public
? 1
: this.frm.meta.make_attachments_public;
}
if (this.df.options) {
Object.assign(options, this.df.options);
}
this.upload_options = options;
}
set_input(value, dataurl) {
this.last_value = this.value;
this.value = value;
if (this.value) {
// value can also be using this format: FILENAME,DATA_URL
// Important: We have to be careful because normal filenames may also contain ","
let file_url_parts = this.value.match(/^([^:]+),(.+):(.+)$/);
let filename;
if (file_url_parts) {
filename = file_url_parts[1];
dataurl = file_url_parts[2] + ":" + file_url_parts[3];
}
if (this.$input && this.$value) {
this.$input.toggle(false);
this.$value
.toggle(true)
.find(".attached-file-link")
.html(filename || this.value)
.attr("href", dataurl || this.value);
} else {
this.$wrapper.html(`
<div class="attached-file flex justify-between align-center">
<div class="ellipsis">
<a href="${dataurl || this.value}" target="_blank">${filename || this.value}</a>
</div>
</div>
`);
}
} else {
this.$input.toggle(true);
this.$value.toggle(false);
}
}
get_value() {
return this.value || null;
}
async on_upload_complete(attachment) {
if (this.frm) {
await this.parse_validate_and_set_in_model(attachment.file_url);
this.frm.attachments.update_attachment(attachment);
// this.frm.doc.docstatus == 1 ? this.frm.save("Update") : this.frm.save();
}
this.set_value(attachment.file_url);
}
toggle_reload_button() {
this.$value
.find('[data-action="reload_attachment"]')
.toggle(this.file_uploader && this.file_uploader.uploader.files.length > 0);
}
};
2 create company.bundle.js file
fiel path —>apps/company/company/public/js/company.bundle.js
import "./attach.js";
3 add bundle.js in hooks
app_include_js = "company.bundle.js"
4 bench build
bench clear-cache
But this is not working ,I want to disable auto save while upload image
Did you found solution for this? I have same problem…
@NCP Can you please provide solution for this…
hyaray
March 21, 2025, 10:35am
12
not yet, directly modify the source code of frappe or erpnext
Please find the reference for this
You can check the following repo Frappe JS Override Example
In the given repo, you can check the app.bundle.js file in which I have overridden the FileUploader.
Also don’t forget to add the app_include_js entry in hooks.py.
If it does not work try rebuilding the assets with the following command bench build --app app_name