Hello,
I've creaed a custom field type called `contractProducts`. The declaration in custom/Espo/Custom/resources/metadata/fields/contractProducts.json is very simple:
The view creates HTML with the following components: A row with a dropdown selection field, three input fields, and a remove button. Underneath is a button to add a new row. This is the associated view file:
When I create a field with this type and add it to a layout, I can press the "+" button, enter information and save. It does properly work BUT ONLY up to THREE elements/rows. If I create a fourth row, I get "POST 500" and "PUT 500".
I also get the following warning:
bullbone.js:1237Could not set element '#main > .record .side .panel[data-name="default"] > .panel-body .field[data-name="complexModified"] [data-name="modifiedBy"]'
This is independent of the number of elements (I get it whenever I save).
I suspect I need to tell the server what kind of data to expect? Maybe the incoming data is bigger than what I implicitly declared it to be?
I would be very thankful for any help/insights!
I've creaed a custom field type called `contractProducts`. The declaration in custom/Espo/Custom/resources/metadata/fields/contractProducts.json is very simple:
Code:
{
"params": [],
"view": "custom:views/fields/contract-products"
}
Code:
define(['views/fields/base', 'underscore'], (BaseFieldView, _) => {
return class extends BaseFieldView {
type = 'contractProducts';
editTemplate = 'custom:fields/contract-products/edit';
detailTemplateContent = `
<ul>
{{#each products}}
<li>{{productName}}: {{quantity}} × {{unitPrice}} = {{amount}}</li>
{{/each}}
</ul>
`;
data() {
return {
products: this.products.map((item) => ({
...item,
selectionOptions: this.getProductOptionsHtml(item.productName),
}))
};
}
setup() {
super.setup();
this.products = this.parseValue(this.model.get(this.name) || '[]');
this.listenTo(this.model, 'change:' + this.name, () => {
this.products = this.parseValue(this.model.get(this.name));
this.reRender();
});
}
fetch() {
const products = [];
this.$el.find('.contract-product-row:not(.contract-product-labels)').each((i, el) => {
const $row = $(el);
const quantity = parseFloat($row.find('[data-field="quantity"]').val()) || 0;
const unitPrice = parseFloat($row.find('[data-field="unit-price"]').val()) || 0;
products.push({
productName: $row.find('[data-field="product-name"]').val(),
quantity: quantity,
listPrice: parseFloat($row.find('[data-field="list-price"]').val()) || 0,
unitPrice: unitPrice,
amount: quantity * unitPrice
});
});
// The data that goes into the server-side model/database
return {
[this.name]: JSON.stringify(products),
};
}
afterRender() {
// TODO: Adding/removing products removes inputs even from fields that are not removed
this.$el.off('click', '.add-product');
this.$el.off('click', '.remove-product');
this.$el.off('input', '[data-field="quantity"], [data-field="unit-price"]');
this.$el.on('click', '.add-product', (e) => {
this.products.push({
productName: '',
quantity: '',
listPrice: '',
unitPrice: '',
amount: '',
});
this.reRender();
});
this.$el.on('click', '.remove-product', (e) => {
const index = $(e.currentTarget).closest('.contract-product-row').data('index');
this.products.splice(index, 1);
this.reRender();
});
this.$el.on('input', '[data-field="quantity"], [data-field="unit-price"]', (e) => {
const $row = $(e.currentTarget).closest('.contract-product-row');
const quantity = parseFloat($row.find('[data-field="quantity"]').val()) || 0;
const unitPrice = parseFloat($row.find('[data-field="unit-price"]').val()) || 0;
const calculatedAmount = (quantity * unitPrice).toFixed(2);
$row.find('[data-field="amount"]').val(calculatedAmount);
});
}
parseValue(raw) {
try {
return JSON.parse(raw) || [];
} catch (e) {
console.error('[DEBUG] Failed to parse value for contractProducts field:', e);
return [];
}
}
getProductOptionsHtml(selectedId) {
const options = [
{ id: 'website', name: 'Website' },
{ id: 'seo', name: 'SEO' },
{ id: 'consulting', name: 'Consulting' }
];
const html = options.map(opt => {
const isSelected = opt.id === selectedId ? 'selected' : '';
return `<option value="${opt.id}" ${isSelected}>${opt.name}</option>`;
}).join('');
return html;
}
};
});
I also get the following warning:
bullbone.js:1237Could not set element '#main > .record .side .panel[data-name="default"] > .panel-body .field[data-name="complexModified"] [data-name="modifiedBy"]'
This is independent of the number of elements (I get it whenever I save).
I suspect I need to tell the server what kind of data to expect? Maybe the incoming data is bigger than what I implicitly declared it to be?
I would be very thankful for any help/insights!

Comment