Announcement

Collapse
No announcement yet.

How to create a custom menu option under the "Edit" button in detail view

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • How to create a custom menu option under the "Edit" button in detail view

    In our application, we have an entity class (scope) called Tenancy, and we wanted to add an option under the "Edit" menu in the Tenancy detail view, that will be available only when the Tenancy has a status "Active" and that when clicked will allow the User to instantiate a process called "Collections Tracker"

    Click image for larger version

Name:	Screenshot 2023-06-03 185022.png
Views:	542
Size:	58.9 KB
ID:	93276

    These are the steps that we took:

    Step 1: Create a custom detail view for "Tenancy" to implement the additional option.
    client/custom/modules/property-management/src/views/tenancy/record/detail.js
    Code:
    define('property-management:views/tenancy/record/detail', 'views/record/detail', function (Dep) {
        return Dep.extend({
            setupActionItems: function() {
    
                // invoke the original setupActionItems function from the parent class
                Dep.prototype.setupActionItems.call(this);
    
                // add the new option to the list
                this.dropdownItemList.push({
                    name: 'instantiateCollectionsTracker',
                    label: 'Instantiate Collections Tracker'
                });
    
                // make the option available only for Tenancies that have a status equal to "Active"
                this.listenToOnce(this.model, 'sync' () => {
                    if(this.model.get('status') !== 'Active' {
                        this.removeButton('instantiateCollectionsTracker');
                    }
                }
    
                // define the action that will take place when the User clicks on the new option
                actionInstantiateCollectionsTracker: function () {
                    let options = {
                        tenancyId: this.model.id
                    }
                    this.getRouter().dispatch().('StateMachineExecution', 'create', options);
                }
    
            }
        });
    });
    Step 2: Create a custom metadata clientDefs file to let the system know that it must use our custom view to render the Tenancy scope in detail display mode.
    application/Espo/Modules/PropertyManagement/Resources/metadata/clientDefs/Tenancy.json
    Code:
    {
        "recordViews": {
            "detail": "property-management:views/tenancy/record/detail"
        }
    }
    Step 3: Clear Cache and Rebuild

    NOTE: The above example refers to files that are under a module namespace (PropertyManagement) but if you don't have your files organized in modules, the file locaitons stated above would be:

    client/custom/src/views/tenancy/record/detail.js

    custom/Espo/Custom/Resources/metadata/clientDefs/Tenancy.json

  • #2
    Variation to the example above:

    How to make the availability of the custom menu option subject to conditions defined in back-end queries.


    For the same "Tenancy" entity, we wanted to add a dropdown menu option: "createServiceTicket", but we wanted to establish the following conditions to make that option available:

    a) There could not be another "ServiceTicket" for the same "Tenancy" with "status" equal to "Received" or "Allocated"
    b) There could not be another "ServiceTicket" for the same "Tenancy", with "isPriority" field value equal to negative, and that was created less than 4 months ago.

    We created the back-end code to perform the appropriate queries and return a boolean value isApproved positive when the situation matched the requirements or negative if they did not.

    Based on Step 1 in the example above, we replaced the code that conditions the existence of the menu option like this:

    Code:
                    // add the new option to the list
                    this.dropdownItemList.push({
                        name: 'createServiceTicket',
                        label: 'Create Service Ticket'
                    });
    
                    // make the option available only for situations that meet the conditions described above
                    this.listenToOnce(this.model, 'sync' () => {
                        // halt the rendering process until this function has produced results, everything else will proceed without delays
                        this.wait(true);
                        // make an Ajax call to fetch the desired data from the backend - a bool value: isApproved
                        const payload = {
                            tenancyId: this.model.id
                        };
                        const url = 'ServiceTicket/action/fetchTicketRedundancy';
                        Espo.Ajax.getRequest(url,payload).then(
                            function (fetchedData) {
                                if(!fetchedData.isApproved) {
                                    this.removeButton('createServiceTicket');
                                }
                                // resume the rendering process
                                this.wait(false);
                            }.bind(this)
                        );
                    }
    
                    // define the action that will take place when the User clicks on the new option
                    actionCreateServiceTicket: function () {
                        let options = {
                            tenancyId: this.model.id
                        }
                        this.getRouter().dispatch().('ServiceTicket', 'create', options);
                    }
    
                }
        });
    });​

    Comment


    • #3
      There's a method utilizing composition rather than inheritance: https://docs.espocrm.com/development...in-detail-view. Meaning multiple extensions can add own dropdown items to the same entity type.

      Comment


      • #4
        Meaning multiple extensions can add own dropdown items to the same entity type.
        That is a great alternative. Here's how our example would look using that approach:

        application/Espo/Modules/PropertyManagement/Resources/metadata/clientDefs/Tenancy.json
        Code:
        {
        
            "detailActionList": [
                "__APPEND__",
                {
                    "label": "Instantiate Collections Tracker",
                    "name": "instantiateCollectionsTracker",
                    "acl": "edit",
                    "data": {
                        "handler": "property-management:tenancy-action-handler"
                    },
                    "initFunction": "initCollectionsTracker"
                }
            ]
        }

        client/custom/modules/property-management/src/tenancy-action-handler.js
        Code:
        define('property-management:tenancy-action-handler', ['action-handler'], function (Dep) {
            return Dep.extend({
        
                // define the action that is taken upon initialization by the action-handler class
                initCollectionsTracker: function () {
                    this.controlCollectionsTrackerVisibility();
                    this.view.listenTo(
                        this.view.model,
                        'change:status',
                        this.controlCollectionsTrackerVisibility.bind(this)
                    );
                },​      
        
                // define the action that controls the visibility of the new dropdown option
                controlCollectionsTrackerVisibility: function () {       
                    if(this.model.get('status') !== 'Active' {
                        this.view.hideActionItem('instantiateCollectionsTracker');
                        return;
                    }
                    this.view.showActionItem('instantiateCollectionsTracker'); 
                },
        
                // define the action that will take place when the User clicks on the new dropdown option
                actionInstantiateCollectionsTracker: function () {
                    let options = {
                        tenancyId: this.view.model.id
                    }
                    this.view.getRouter().dispatch().('StateMachineExecution', 'create', options);
                }​        
            });
        });

        Comment

        Working...
        X