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