How to override standard js class

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?

@Rehan_Ansari Please refer this.

it is not a doctype class it is a javascript class?

You have to check the example of hrms:

custom js file

hooks.py
image

I added like this but not working it is correct?

No.

Again check the example of india-compliance app.

1 Like

How to override js class in frappe>

  1. 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;

  1. Created project_name.bundle.js file >
import "./attach.js";
  1. Added in Hooks >
app_include_js = [
        "project_name.bundle.js"
        
    ]

Thanks @NCP for guidance :+1:

3 Likes

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.

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