Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save vr-greycube/6e7576d5cf9533f4524a8bc10a3faba4 to your computer and use it in GitHub Desktop.
Save vr-greycube/6e7576d5cf9533f4524a8bc10a3faba4 to your computer and use it in GitHub Desktop.
frappe.ui.form.on('Travel Request', {
onload(frm) {
new StatusStepper(frm, {
grid_fields: ['itinerary'],
fieldnames: ['travel_to', 'mode_of_travel']
}).init()
}
})
class StatusStyler {
constructor() {
this.btnTemplate = BUTTON_TEMPLATE
this.style_css = STATUS_STYLE_CSS
this.init()
}
init() {
frappe.dom.set_style(this.style_css, 'status-style')
}
getSelector(prefix, field) {
return `${prefix}[data-fieldname="${field}"]`
}
getNextStatus(current) {
const index = STATUS_OPTIONS.indexOf(current);
return (index === -1 || index === STATUS_OPTIONS.length - 1)
? STATUS_OPTIONS[0]
: STATUS_OPTIONS[index + 1];
}
applyStyle($elements, status) {
if (!status) return;
const safeClass = 'status-' + frappe.scrub(status);
const elements = ($elements instanceof jQuery ? $elements.toArray() : Array.isArray($elements) ? $elements : [$elements]);
elements.forEach(el => {
const $el = $(el);
if ($el.length) {
const newClassList = ($el.attr('class') || '')
.split(/\s+/)
.filter(c => !c.startsWith('status-'))
.concat(safeClass)
.join(' ');
$el.attr('class', newClassList);
}
});
}
injectStatusButton($target, click_handler) {
if ($target.find('.float-status-btn').length > 0) {
return $target.find('.float-status-btn').first();
}
const $btn = $(this.btnTemplate);
$target.append($btn);
$btn.on('click', frappe.utils.throttle(() => {
if (typeof click_handler === 'function') {
click_handler($target)
}
}, 500))
return $btn;
}
}
class StatusStepper {
constructor(frm, { grid_fields = [], fieldnames = [] } = {}) {
this.frm = frm
this.grid_fields = grid_fields
this.fieldnames = fieldnames
this.styler = new StatusStyler()
}
init = () => {
this.bind_on_grid_row_render()
}
bind_on_grid_row_render = () => {
$(this.frm.wrapper).on('grid-row-render', (e, grid_row) => {
if (!this.grid_fields.includes(grid_row.parent_df.fieldname)) return
this.fieldnames
.filter(field => grid_row.columns[field])
.forEach(field => {
const selector = this.styler.getSelector('.col', field)
const $el = grid_row.wrapper.find(selector)
this.styler.applyStyle($el, grid_row.doc[field])
this.styler.injectStatusButton($el, $target => {
const current_value = frappe.model.get_value(grid_row.doc.doctype, grid_row.doc.name, field)
const next_value = this.styler.getNextStatus(current_value)
frappe.model.set_value(grid_row.doc.doctype, grid_row.doc.name, field, next_value).then(() => {
this.styler.applyStyle($el, grid_row.doc[field])
})
})
})
})
}
}
const STATUS_OPTIONS = [
"To Do",
"Pass",
"Fail Minor",
"Fail Major",
"Undetermined",
]
const BUTTON_TEMPLATE = `
<button class="float-status-btn" title="Change Status">
<svg class="icon icon-xs" aria-hidden="true">
<use href="#icon-arrow-right"></use>
</svg>
</button>
`;
const STATUS_STYLE_CSS = `
.float-status-btn {
position: absolute;
top: 5px;
right: 5px;
background: none;
border: none;
cursor: pointer;
padding: 2px;
z-index: 10;
}
.float-status-btn svg {
fill: #333;
width: 14px;
height: 14px;
}
.float-status-btn:hover svg {
fill: #007bff;
}
.status-to_do {
background-color: #B3E5FC!important
}
.status-pass {
background-color: #DCE775!important
}
.status-fail_minor {
background-color: #FFD54F!important
}
.status-fail_major {
background-color: #EF9A9A!important
}
.status-undetermined {
background-color: #E0E0E0!important
}
.col .field-area input {
background-color: transparent !important;
border: none;
box-shadow: none;
}
`
@vr-greycube
Copy link
Author

child table customization

using the 'grid-row-render' event emitted by grid_row, to modify specific fields in the grid row

  • set a class to apply custom styles e.g. change the background color
  • add an action button for custom actions (e.g. moving the current status to the next one)
image

@vr-greycube
Copy link
Author

vr-greycube commented Apr 26, 2025

this looks interesting custom formatter

something like this may be used to step through values

frappe.meta.docfield_map['Travel Itinerary'].custom_trip_status.formatter = (value) => {
    const _class = 'status-' + frappe.scrub(value);
    return `<button class="btn btn-primary ${_class}" onclick="custom_fn_to_set_value_and_style()">${value}</button>`;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment