Last active
April 28, 2025 04:02
-
-
Save vr-greycube/6e7576d5cf9533f4524a8bc10a3faba4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
` |
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
child table customization
using the 'grid-row-render' event emitted by grid_row, to modify specific fields in the grid row