Creating a new percentage Field?

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Marcel
    Junior Member
    • Jul 2025
    • 11

    #1

    Creating a new percentage Field?

    Hi all,

    is there a way to create a new percentage Field based on the float field? Just want to see the value like 5,45 % instead of 5,45 in the layout.

    Thanks!
  • item
    Active Community Member
    • Mar 2017
    • 1538

    #2
    Hi,

    i am not specialist of front-end but you can begin with this working code (espoCrm v9.1.8)
    it's a little different because he color if value is < 0 or = 0 or > 0. but you have the direction of how to do.

    create a file : client/custom/src/views/fields/pourcent-float.js

    PHP Code:

    define
    (['views/fields/float'], (FloatFieldView) => {

       return class extends 
    FloatFieldView {

            
    setup () {
                
    super.setup();
            }

            
    afterRender () {
                
    super.afterRender();
                if (
    this.mode == 'search') return;
                
    let value parseFloat(this.model.get(this.name));

                if (
    this.mode =='list'){
                    
    this.$el.text('');
                    
    this.$el
                        
    .addClass(this.getListClass(value))
                        .
    append($('<span>').text(value.toFixed(2) + '%'));
                }else{
                    
    this.$el.text('');
                    
    this.$el.append(this.getSpanTag(value) );
                }

                
    this.$el.on('change', (e) => {
                    
    let value parseFloat(this.model.get(this.name));
                    if (
    this.mode =='list'){
                        
    this.$el.text('');
                        
    this.$el
                            
    .addClass(this.getListClass(value))
                            .
    append($('<span>').text(value.toFixed(2) + '%'));
                    }else{
                        
    this.$el.text('');
                        
    this.$el.append(this.getSpanTag(value) );
                    }
                });
            }

            
    getListClassvalue ){
                if ( 
    value 0) {
                    return 
    'text-primary';
                }

                if (
    value 0.001 && value > -0.001 ) {
                    return 
    'text-success';
                }

                if (
    value 0){
                    return 
    'text-danger'
                
    }

            }

            
    getSpanTag value ){
                if ( 
    value 0) {
                    return $(
    '<span>')
                                .
    addClass('label label-md label-primary')
                                .
    text(value.toFixed(2) + '%');
                }

                if (
    value 0.001 && value > -0.001 ) {
                    return $(
    '<span>')
                                .
    addClass('label label-md label-success')
                                .
    text(value.toFixed(2) + '%');
                }

                if (
    value 0){
                    return $(
    '<span>')
                                .
    addClass('label label-md label-danger')
                                .
    text(value.toFixed(2) + '%');
                }
            }
        }
    }); 
    And in your entityDefs (where the field must be append with %) here a sample field "balance" :

    path : custom/Espo/Custom/Resources/metadata/entityDefs/Contact.json. // for sample in Contact.json

    PHP Code:

            
    "balance": {
                
    "notNull"false,
                
    "type""float",
                
    "default"0,
                
    "readOnly"true,
                
    "isCustom"true,
                
    "duplicateIgnore"true,
                
    "decimalPlaces"2,
                
    "view""custom:views/fields/pourcent-float",   // only add this line
                
    "audited"true
            
    }, 
    clearCache, rebuid... clear browser (maybe) .. and you will see result
    Last edited by item; 07-25-2025, 07:27 PM.
    If you could give the project a star on GitHub. EspoCrm believe our work truly deserves more recognition. Thanks.​

    Comment

    • Marcel
      Junior Member
      • Jul 2025
      • 11

      #3
      Originally posted by item
      Hi,

      i am not specialist of front-end but you can begin with this working code (espoCrm v9.1.8)
      it's a little different because he color if value is < 0 or = 0 or > 0. but you have the direction of how to do.

      create a file : client/custom/src/views/fields/pourcent-float.js

      PHP Code:

      define
      (['views/fields/float'], (FloatFieldView) => {

      return class extends 
      FloatFieldView {

      setup () {
      super.setup();
      }

      afterRender () {
      super.afterRender();
      if (
      this.mode == 'search') return;
      let value parseFloat(this.model.get(this.name));

      if (
      this.mode =='list'){
      this.$el.text('');
      this.$el
      .addClass(this.getListClass(value))
      .
      append($('').text(value.toFixed(2) + '%'));
      }else{
      this.$el.text('');
      this.$el.append(this.getSpanTag(value) );
      }

      this.$el.on('change', (e) => {
      let value parseFloat(this.model.get(this.name));
      if (
      this.mode =='list'){
      this.$el.text('');
      this.$el
      .addClass(this.getListClass(value))
      .
      append($('').text(value.toFixed(2) + '%'));
      }else{
      this.$el.text('');
      this.$el.append(this.getSpanTag(value) );
      }
      });
      }

      getListClassvalue ){
      if ( 
      value 0) {
      return 
      'text-primary';
      }

      if (
      value 0.001 && value > -0.001 ) {
      return 
      'text-success';
      }

      if (
      value 0){
      return 
      'text-danger'
      }

      }

      getSpanTag value ){
      if ( 
      value 0) {
      return $(
      '')
      .
      addClass('label label-md label-primary')
      .
      text(value.toFixed(2) + '%');
      }

      if (
      value 0.001 && value > -0.001 ) {
      return $(
      '')
      .
      addClass('label label-md label-success')
      .
      text(value.toFixed(2) + '%');
      }

      if (
      value 0){
      return $(
      '')
      .
      addClass('label label-md label-danger')
      .
      text(value.toFixed(2) + '%');
      }
      }
      }
      }); 
      And in your entityDefs (where the field must be append with %) here a sample field "balance" :

      path : custom/Espo/Custom/Resources/metadata/entityDefs/Contact.json. // for sample in Contact.json

      PHP Code:

      "balance": {
      "notNull"false,
      "type""float",
      "default"0,
      "readOnly"true,
      "isCustom"true,
      "duplicateIgnore"true,
      "decimalPlaces"2,
      "view""custom:views/fields/pourcent-float"// only add this line
      "audited"true
      }, 
      clearCache, rebuid... clear browser (maybe) .. and you will see result
      Hi item

      Thanks a lot! That helped!

      I created this script out of it and I am currently trying to create that a bit more modular. I want to set parameters per field in the entityDefs file like

      PHP Code:

        
      "interestRate": {  "notNull"false,  "type""float",  "required"false,  "audited"true,  "max"100,  "decimalPlaces"2,  "isCustom"true,  "tooltip"true,  "view""custom:views/fields/percent-float",  "useColorLabel"true,  "useColorText"true,  "useRenderOn": ["list""detail"]  } 

      is that somehow possible?

      I tried a bit arround. See my comments in the Code

      PHP Code:

        
      /**   * Extend the default FloatFieldView to render percentage values with optional color styling  *   * Example "../metadata/entityDefs/[ENTITYNAME].json" field  * "interestRate": {  * "type": "float",  * "decimalPlaces": 2,  * "max": 100,  * "isCustom": true,  * "view": "custom:views/fields/percent-float", // This needs to be added  * "useColorLabel": true, // This can be added if you want to use label colors. If you don´t set it, it will be false  * "useColorText": true, // This can be added if you want to use text colors. If you don´t set it, it will be false  * "useRenderOn": ["list", "detail"] // This can be used to define where the formatted render should be used. If missing or empty, nothing will be rendered.  * }  */    define(['views/fields/float'], (FloatFieldView) => {  return class extends FloatFieldView {    setup () {  super.setup();  }    afterRender () {  super.afterRender();    // Safety check for unexpected call contexts  if (!this.model || !this.name || typeof this.model.get !== 'function') return;    const value = parseFloat(this.model.get(this.name));  if (isNaN(value)) return;    /**   * Read target render modes from metadata.  *   * Supported render modes in EspoCRM for field views:  *   * | Mode | Meaning / Usage Context |  * | ------------ | ----------------------------------------------------------- |  * | `list` | Entity list view (grid/table showing multiple records) |  * | `detail` | Detail view of a single record (read-only display) |  * | `edit` | Edit mode (form for editing a record) |  * | `search` | Search/filter form mode (filter fields in list search) |  * | `massUpdate` | Mass update mode (bulk editing dialog for multiple records) |  * | `kanban` | Kanban board view mode (card style layout for records) |  * | `preview` | Preview mode (e.g. quick view panels or preview popups) |  */    // Get field metadata from entityDefs via Metadata API  //const entityDefs = this.model.getMetadata().get('entityDefs')[this.model.name] || {};  //const fieldDefs = (entityDefs.fields && entityDefs.fields[this.name]) ? entityDefs.fields[this.name] : {};    // Try different places to get field definitions  //const fieldDefs = this.def || this.fieldDefs || {};    const renderModes = Array.isArray(this.params.useRenderOn) ? this.params.useRenderOn : ['list', 'detail'];  //const renderModes = Array.isArray(this.params.useRenderOn) ? this.params.useRenderOn : [];  //const renderModes = Array.isArray(fieldDefs.useRenderOn) ? fieldDefs.useRenderOn : [];    // Only render if useRenderOn is set and includes current mode  if (renderModes.length === 0 || !renderModes.includes(this.mode)) {  return; // Do not render formatted percentage  }    const $span = $('<span>').text(value.toFixed(2) + '%');    // Read field parameters from metadata  const useLabel = this.params.useColorLabel === true;  const useText = this.params.useColorText === true;  //const useLabel = fieldDefs.useColorLabel === true;  //const useText = fieldDefs.useColorText === true;    if (useLabel) {  $span.addClass(this.getLabelClass(value));  }    if (useText) {  $span.addClass(this.getTextClass(value));  }    this.$el.empty().append($span);  }    /**  * Bootstrap label classes for badge-style color background.  *   * | Class Name | Description |  * | --------------- | ------------------------------------- |  * | label-default | Gray (neutral) |  * | label-primary | Dark blue (informational or negative)|  * | label-success | Green (positive/success) |  * | label-warning | Orange (caution) |  * | label-danger | Red (critical/negative) |  * | label-info | Light blue (general info) |  *   * Conditions can be adjusted in getLabelClass().  */  getLabelClass(value) {  if (value < 0) return 'label label-md label-primary';  if (value > 0) return 'label label-md label-danger';  return 'label label-md label-success';  }    /**  * Bootstrap text color classes (affects text color only).  *   * | Class Name | Description |  * | -------------- | ---------------------------------- |  * | text-muted | Light gray (subtle/disabled) |  * | text-primary | Dark blue (informational or negative) |  * | text-success | Green (positive/success) |  * | text-info | Light blue (neutral info) |  * | text-warning | Orange (warning) |  * | text-danger | Red (critical/negative) |  *   * Conditions can be adjusted in getTextClass().  */  getTextClass(value) {  if (value < 0) return 'text-primary';  if (value > 0) return 'text-danger';  return 'text-success';  }  }  });
      ​ 

      Comment

      • Marcel
        Junior Member
        • Jul 2025
        • 11

        #4
        Hi item

        Thanks a lot! That helped!

        I created this script out of it and I am currently trying to create that a bit more modular. I want to set parameters per field in the entityDefs file like

        PHP Code:

                
        "interestRate": {
                    
        "notNull"false,
                    
        "type""float",
                    
        "required"false,
                    
        "audited"true,
                    
        "max"100,
                    
        "decimalPlaces"2,
                    
        "isCustom"true,
                    
        "tooltip"true,
                    
        "view""custom:views/fields/percent-float",
                    
        "useColorLabel"true,
                    
        "useColorText"true,
                    
        "useRenderOn": ["list""detail"]
                } 
        is that somehow possible?

        I tried a bit arround. See my comments in the Code

        PHP Code:

        /**
         * Extend the default FloatFieldView to render percentage values with optional color styling
         *
         * Example "../metadata/entityDefs/[ENTITYNAME].json" field
         * "interestRate": {
         *     "type": "float",
         *     "decimalPlaces": 2,
         *     "max": 100,
         *     "isCustom": true,
         *     "view": "custom:views/fields/percent-float",     // This needs to be added
         *     "useColorLabel": true,                           // This can be added if you want to use label colors. If you don´t set it, it will be false
         *     "useColorText": true,                            // This can be added if you want to use text colors. If you don´t set it, it will be false
         *     "useRenderOn": ["list", "detail"]                // This can be used to define where the formatted render should be used. If missing or empty, nothing will be rendered.
         * }
         */

        define(['views/fields/float'], (FloatFieldView) => {
            return class extends 
        FloatFieldView {

                
        setup () {
                    
        super.setup();
                }

                
        afterRender () {
                    
        super.afterRender();

                    
        // Safety check for unexpected call contexts
                    
        if (!this.model || !this.name || typeof this.model.get !== 'function') return;

                    const 
        value parseFloat(this.model.get(this.name));
                    if (
        isNaN(value)) return;

                    
        /**
                     * Read target render modes from metadata.
                     *
                     * Supported render modes in EspoCRM for field views:
                     *
                     * | Mode         | Meaning / Usage Context                                     |
                     * | ------------ | ----------------------------------------------------------- |
                     * | `list`       | Entity list view (grid/table showing multiple records)      |
                     * | `detail`     | Detail view of a single record (read-only display)          |
                     * | `edit`       | Edit mode (form for editing a record)                       |
                     * | `search`     | Search/filter form mode (filter fields in list search)      |
                     * | `massUpdate` | Mass update mode (bulk editing dialog for multiple records) |
                     * | `kanban`     | Kanban board view mode (card style layout for records)      |
                     * | `preview`    | Preview mode (e.g. quick view panels or preview popups)     |
                     */

                    // Get field metadata from entityDefs via Metadata API
                    //const entityDefs = this.model.getMetadata().get('entityDefs')[this.model.name] || {};
                    //const fieldDefs = (entityDefs.fields && entityDefs.fields[this.name]) ? entityDefs.fields[this.name] : {};

                    // Try different places to get field definitions
                    //const fieldDefs = this.def || this.fieldDefs || {};

                    
        const renderModes = Array.isArray(this.params.useRenderOn) ? this.params.useRenderOn : ['list''detail'];
                    
        //const renderModes = Array.isArray(this.params.useRenderOn) ? this.params.useRenderOn : [];
                    //const renderModes = Array.isArray(fieldDefs.useRenderOn) ? fieldDefs.useRenderOn : [];

                    // Only render if useRenderOn is set and includes current mode
                    
        if (renderModes.length === || !renderModes.includes(this.mode)) {
                        return; 
        // Do not render formatted percentage
                    
        }

                    const 
        $span = $('<span>').text(value.toFixed(2) + '%');

                    
        // Read field parameters from metadata
                    
        const useLabel this.params.useColorLabel === true;
                    const 
        useText this.params.useColorText === true;
                    
        //const useLabel = fieldDefs.useColorLabel === true;
                    //const useText = fieldDefs.useColorText === true;

                    
        if (useLabel) {
                        
        $span.addClass(this.getLabelClass(value));
                    }

                    if (
        useText) {
                        
        $span.addClass(this.getTextClass(value));
                    }

                    
        this.$el.empty().append($span);
                }

                
        /**
                 * Bootstrap label classes for badge-style color background.
                 *
                 * | Class Name      | Description                             |
                 * | --------------- | ------------------------------------- |
                 * | label-default   | Gray (neutral)                        |
                 * | label-primary   | Dark blue (informational or negative)|
                 * | label-success   | Green (positive/success)              |
                 * | label-warning   | Orange (caution)                      |
                 * | label-danger    | Red (critical/negative)               |
                 * | label-info      | Light blue (general info)             |
                 *
                 * Conditions can be adjusted in getLabelClass().
                 */
                
        getLabelClass(value) {
                    if (
        value 0) return 'label label-md label-primary';
                    if (
        value 0) return 'label label-md label-danger';
                    return 
        'label label-md label-success';
                }

                
        /**
                 * Bootstrap text color classes (affects text color only).
                 *
                 * | Class Name     | Description                          |
                 * | -------------- | ---------------------------------- |
                 * | text-muted     | Light gray (subtle/disabled)       |
                 * | text-primary   | Dark blue (informational or negative) |
                 * | text-success   | Green (positive/success)            |
                 * | text-info      | Light blue (neutral info)           |
                 * | text-warning   | Orange (warning)                    |
                 * | text-danger    | Red (critical/negative)             |
                 *
                 * Conditions can be adjusted in getTextClass().
                 */
                
        getTextClass(value) {
                    if (
        value 0) return 'text-primary';
                    if (
        value 0) return 'text-danger';
                    return 
        'text-success';
                }
            }
        }); 

        Comment

        Working...