Custom Field Type: Cannot save with more than 3 Elements

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • BonfireAtNight
    Junior Member
    • Apr 2025
    • 7

    Custom Field Type: Cannot save with more than 3 Elements

    Hello,

    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"
    }
    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:
    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;
            }
          
        };
    });​​
    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!
  • BonfireAtNight
    Junior Member
    • Apr 2025
    • 7

    #2
    I just realized that the database column for my contractProducts field is of type VARCHAR.

    I think the database entry was created when I used the Entry Manager and created a new field with the new custom type, right? This is the respective entityDefs entry:
    [CODE]
    "cContractProducts": {
    "type": "contractProducts",
    "isCustom": true
    }
    [CODE]

    I think my main question in this thread should be: How do I define the database column type for new custom field types? I think in my case it should be something like JSON?

    Comment

    • macistda
      Member
      • Jul 2022
      • 93

      #3
      Use Entity Editor, that does the editing for you:

      Comment

      Working...