Announcement

Collapse
No announcement yet.

Reference: Espo GUI - script map guide, where can I change something ?

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

  • rabii
    commented on 's reply
    Thanks telecastg for sharing

  • telecastg
    commented on 's reply
    Thanks shalmaxb !

  • shalmaxb
    commented on 's reply
    telecastg, great as always

  • telecastg
    replied
    How to transpose columns to rows in a list view ?

    Based on a question posted by rem4332 and a hint provided by shalmaxb this is how to transpose columns and rows in a table view:

    Click image for larger version  Name:	Transpose View Initial.jpg Views:	0 Size:	64.7 KB ID:	78724

    Click image for larger version  Name:	Transpose View Menu.jpg Views:	0 Size:	72.3 KB ID:	78725
    Click image for larger version  Name:	Transpose View Transposed.jpg Views:	0 Size:	64.0 KB ID:	78726

    1) Create a custom list view class to render the target entity's list display: (In this example the entity is ServiceTech)

    client/custom/src/views/service-tech/record/list.js
    Code:
    define('custom:views/service-tech/record/list', 'views/record/list', function (Dep) {
    
    return Dep.extend({
    
    dropdownItemList: [
    {
    name: 'transposeColumnsToRows',
    label: 'Transpose Columns to Rows'
    },
    {
    name: 'resetTransposition',
    label: 'Reset Transposition'
    }
    ],
    
    actionTransposeColumnsToRows: function () {
    $('tr.list-row').css("display","block");
    $('tr.list-row').css("float","left");
    $('td.cell').css("display","block");
    $('th').css("display","none");
    },
    
    actionResetTransposition: function () {
    $('tr.list-row').css("display","");
    $('tr.list-row').css("float","");
    $('td.cell').css("display","");
    $('th').css("display","");
    }
    
    });
    
    });
    2) Create custom clientDefs file to let Espo know that the above view class should be invoked when rendering a list of service Techs

    custom/Espo/Custom/Resources/metadata/clientDefs/ServiceTech.json
    Code:
    {
    "recordViews": {
    "list": "custom:views/service-tech/record/list"
    }
    }
    3) Clear cache and rebuild
    Last edited by telecastg; 03-13-2022, 07:48 PM.

    Leave a comment:


  • telecastg
    replied
    How is data persisted in the database when I click the "Save" button in an entity edit view ?

    Click image for larger version  Name:	Screenshot 2022-01-27 143308.png Views:	0 Size:	73.0 KB ID:	78528

    Clicking the "Save" button above triggers the following workflow:
    L Script Method Description
    0 client/src/views/record/detail.js actionSave() Invokes action save()
    1 client/src/views/record/base.js save() Invokes action fetch()
    2 client/src/views/record/base.js fetch() Requests the "view" class for each field in the form
    3 client/src/views/record/detail.js getFieldViews() Returns the "view" class for all fields in all sections in the form
    4 client/src/views/record/detail-middle.js getFieldViews() Returns the "view" classes for the fields in the "middle" section
    4 client/src/views/record/panels/side.js getFieldViews() Returns the "view" classes for the fields in the "side" section
    4 client/src/views/record/panels/bottom.js getFieldViews() Returns the "view" classes for the fields in the "bottom" section
    2 client/src/views/record/base.js fetch() Extracts the data from each field and returns as a "setAttribbutes" object
    1 client/src/views/record/base.js save() Invokes action set()
    2 client/src/model.js set() updates the model attribute values
    1 client/src/views/record/base.js save() Invokes action save()
    2 Backbone.model save() Invokes action sync()
    3 Backbone sync() uses jQuery.ajax to make a RESTful JSON request and returns a jqXHR
    4 application\Espo\Core\Controllers\RecordBase.php postActionCreate() If its a new record, Invokes method create() at the entity's back end Service class
    4 application\Espo\Core\Controllers\RecordBase.php putActionUpdate() If its an update, invokes method update() at the entity's back end Service class
    Last edited by telecastg; 01-29-2022, 09:42 PM.

    Leave a comment:


  • telecastg
    replied
    How to display different bottom (Relationship) panels in a Detail view depending on the value of one of the Entity's (Record) field

    Replying to my friend and active contributor item question above, here is how you can toggle between different relationship panels in response to a change of one of the entity's field values.

    In this example, when the client type is "Auditor" there are 2 relationship panels: "Treatments" and "Claims", when the client type is "Consumer" only the panel "Treatments" is displayed and when the client type is "Institution" only the panel "Claims" is displayed.

    Click image for larger version  Name:	Screenshot 2022-01-22 234008.png Views:	0 Size:	33.3 KB ID:	78429

    Click image for larger version  Name:	Screenshot 2022-01-22 234219.png Views:	0 Size:	30.4 KB ID:	78430

    Click image for larger version  Name:	Screenshot 2022-01-22 234400.png Views:	0 Size:	30.0 KB ID:	78431

    To accomplish this, follow the steps below:

    1) Create custom record detail view class client/custom/src/views/system-client/record/detail.js
    Code:
    define('custom:views/system-client/record/detail', 'views/record/detail', function (Dep) {
    
        return Dep.extend({
    
            // define the specifications for the auditor bottom (relationship) panel
            auditorBottomPanelsDetail:
            {
                "_delimiter_": {
                    "disabled": true
                },
                "treatments": {
                    "index": 0
                },
                "claims": {
                    "index": 1
                }
            },
    
            // define the specifications for the consumer bottom (relationship) panel
            consumerBottomPanelsDetail:
            {
                "_delimiter_": {
                    "disabled": true
                },
                "treatments": {
                    "index": 0
                }
            },
    
            // define the specifications for the institution bottom (relationship) panel
            institutionBottomPanelsDetail:
            {
                "_delimiter_": {
                    "disabled": true
                },
                "claims": {
                    "index": 0
                }
            },
    
            // initialize a layout data container object
            layoutData: {},
    
            // point to the custom bottom (relationship) panel view class
            bottomView: "custom:views/system-client/record/detail-bottom",
    
            setup: function() {
                // invoke the original method from the extend base view class
                Dep.prototype.setup.call(this);
                 // render the appropriate bottom panels for the existing record
                this.toggleBottomPanel();
                // add code to switch the bottom panels based on the value of the control field
                this.listenTo(this.model, "change:clientType", function() {
                    this.toggleBottomPanel();
                }, this);
            },
    
            // define a custom method to create the bottom (relationship) panel view instance
            createBottomView: function () {
                var el = this.options.el || '#' + (this.id);
                this.createView('bottom', this.bottomView, {
                    model: this.model,
                    scope: this.scope,
                    el: el + ' .bottom',
                    readOnly: this.readOnly,
                    type: this.type,
                    inlineEditDisabled: this.inlineEditDisabled,
                    recordHelper: this.recordHelper,
                    recordViewObject: this,
                    portalLayoutDisabled: this.portalLayoutDisabled,
                    layoutData: this.layoutData
                }, function(bottomView){
                    bottomView.render();
                });
            },
    
            toggleBottomPanel: function () {
                const clientType = this.model.get("clientType");
                if(clientType === "Consumer") {
                    this.layoutData = this.consumerBottomPanelsDetail;
                } else if (clientType === "Institution") {
                    this.layoutData = this.institutionBottomPanelsDetail;
                } else if (clientType === "Auditor") {
                    this.layoutData = this.auditorBottomPanelsDetail;
                }
                this.createBottomView();
            }
    
        });
    });
    2) Create custom bottom section view class client/custom/src/views/system-client/record/detail-bottom.js
    Code:
    define('custom:views/system-client/record/detail-bottom', 'views/record/detail-bottom', function (Dep) {
    
        return Dep.extend({
    
            // function adapted from views/record/detail-bottom
            setup: function () {
    
                this.type = this.mode;
    
                if ('type' in this.options) {
                    this.type = this.options.type;
                }
    
                this.panelList = [];
    
                this.setupPanels();
    
                this.layoutData = this.options.layoutData;
    
                let panelNameList = [];
    
                this.panelList = this.panelList.filter((p) => {
                    panelNameList.push(p.name);
    
                    if (p.aclScope) {
                        if (!this.getAcl().checkScope(p.aclScope)) {
                            return;
                        }
                    }
    
                    if (p.accessDataList) {
                        if (!Espo.Utils.checkAccessDataList(p.accessDataList, this.getAcl(), this.getUser())) {
                            return false;
                        }
                    }
    
                    return true;
                });
    
                if (this.relationshipPanels) {
                    let linkDefs = (this.model.defs || {}).links || {};
                    if (this.layoutData) {
                        for (const name in this.layoutData) {
                            if (!linkDefs[name]) {
                                continue;
                            }
    
                            let p = this.layoutData[name];
    
                            if (!~panelNameList.indexOf(name) && !p.disbled) {
                                this.addRelationshipPanel(name, p);
                            }
                        }
                    }
                }
    
                this.panelList = this.panelList.map((p) => {
                    let item = Espo.Utils.clone(p);
    
                    if (this.recordHelper.getPanelStateParam(p.name, 'hidden') !== null) {
                        item.hidden = this.recordHelper.getPanelStateParam(p.name, 'hidden');
                    }
                    else {
                        this.recordHelper.setPanelStateParam(p.name, 'hidden', item.hidden || false);
                    }
                    return item;
                });
    
                this.panelList.forEach((item) => {
                    item.actionsViewKey = item.name + 'Actions';
                });
    
                this.alterPanels();
    
                this.setupPanelsFinal();
    
                this.setupPanelViews();
    
            }
    
        });
    });
    3) Create custom clientDefs json definition custom/Espo/Custom/Resources/metadata/clientDefs/Test3.json
    Code:
    {
        "controller": "controllers/record",
        "recordViews": {
            "detail": "custom:views/system-client/record/detail"
        },
        "boolFilterList": [
            "onlyMy"
        ]
    }
    4) Clear cache and rebuild.
    Last edited by telecastg; 01-23-2022, 05:03 PM.

    Leave a comment:


  • telecastg
    replied
    How to create two different layouts for displaying a single entity and apply either layout depending on a value of the entity's (record's) attribute (field)

    Please note that the following example had been extremely simplified, and it is not fully tested nor optimized for highest speed of execution, since the objective is only to show how to create two custom layouts and apply them dynamically, not the actual solution to the hypothetical situation presented.

    Also note that in this implementation, changing the layout requires a display reRender(), every time that the value of the target field changes, so the entity is automatically saved and that interferes with the inline editing and normal saving mechanism, therefore this example must be considered as partially implemented ONLY.

    In the real world, such a simple variation, like the one used in this example (show a different field based on the value of another field), could easily be achieved using dynamic logic or dynamic handler to manipulate the visibility of fields instead of having a custom layout for each case.

    However, using custom layouts for different cases would allow not only to hide or display fields, but to actually have a complete different distribution of fields and panels in the screen like changing templates, so for those interested in such capability this can be a good starting point

    For this example, we assume the existence of an entity called SystemClient, which has, amongst its attributes, an enum field called clientType.

    The field clientType has two possible values: Consumer and Institution and depending on this value, we will want to use a "consumerDetailLayout" or an "institutionDetailLayout" to render the Client record.

    the "consumerDetailLayout" will include a link field to ratings of Medical Institutions called "institutionRatings" and the "institutionalDetailLayout" will include instead a link to a history of medical insurance claims by consumers called "consumerRatings"

    Step 1: create a custom view class with the following content:

    client/custom/src/views/system-client/record/detail.js
    Code:
    Espo.define('custom:views/system-client/record/detail', 'views/record/detail', function (Dep) {
    
        return Dep.extend({
    
            /* define a default display layout */
                defaultLayout: [
                    {
                        "rows": [
                            [
                                {
                                    "name": "name"
                                },
                                    false
                            ],
                            [
                                {
                                    "name": "clientType"
                                },
                                false
                            ]
                        ],
                        "style": "default",
                        "label": "Client Data"
                    }
                ],
    
                /* define here the specifications for the consumer display,
                 note that you can choose not only what fields are displayed but also the actual placement of the fields within each panel and even having a different number of panels for                 each layout */
    
                consumerDetailLayout: [
                    {
                        "rows": [
                            [
                                {
                                    "name": "name"
                                },
                                false
                            ],
                            [
                                {
                                    "name": "clientType"
                                },
                                {
                                    "name": "institutionRatings"
                                }
                            ]
                        ],
                        "style": "default",
                        "label": "Consumer Data"
                    }      
                ],
    
                /* define here the specifications for the institution display */
    
                institutionDetailLayout: [
                    {
                        "rows": [
                            [
                                {
                                    "name": "name"
                                },
                                false
                            ],
                            [
                                {
                                    "name": "clientType"
                                },
                                {
                                    "name": "consumerRatings"
                                }
                            ]
                        ],
                        "style": "default",
                        "label": "Institution Data"
                    }
                ],
    
            setup: function() {
                // invoke the original method from the extend base view class
                Dep.prototype.setup.call(this);
                this.detailLayout = this.defaultLayout;
                // add code to select the appropriate layout based on the value of the field and rebuild the display
                const self = this;
                self.listenToOnce(self, "after:render", function(){
                    self.listenTo(self.model, "change:clientType", function() {
                        let clientType = self.model.get("clientType");
                        self.model.save();
                        if(clientType === "Consumer") {
                            self.detailLayout = self.consumerDetailLayout;
                        } else if (clientType === "Institution") {
                            self.detailLayout = self.institutionDetailLayout;
                        } else {
    
                        }
                        this.gridLayout = null;
                        self.build();
                        self.reRender();
                    });
                });
            }
        });
    });
    Step 2: modify the custom entity's clientDefs script to let Espo know that it should invoke our custom view class to render a detail or edit display and not the default core view class.

    custom/Espo/Custom/Resources/metadata/clientDefs/SystemClient.json
    Code:
    {
        "recordViews": {
            "detail": "custom:views/system-client/record/detail"
        }
    }
    Last edited by telecastg; 01-23-2022, 04:26 AM.

    Leave a comment:


  • telecastg
    commented on 's reply
    Hello @item,

    I think that the easiest way would be to include all fields in one layout and then use dynamic handler or dynamic logic to hide or display fields based on the type of subject (Patient or Institution)

    To use a custom layout for different subjects, instead of manipulating one layout through dynamic logic or dynamic handler, would require some coding, I'll write the instructions in a separate post so it's easier to find by other users that have the same requirements.

  • telecastg
    replied
    What is the program flow to render a single record(entity) in Espo ?

    Following up on the previous post "How are new records created ?" https://forum.espocrm.com/forum/deve...1398#post61398 the view class client/src/views/record/detail.js is invoked to render the panels and fields which comprise the display to view or edit a single entity.

    This is the program flow executed within client/src/views/record/detail.js to render the record (entity) panels and fields (attributes):

    Click image for larger version  Name:	Screenshot 2022-01-19 110719.png Views:	56 Size:	40.1 KB ID:	78349

    1) After executing the core methods init() and setup() the core method afterSetup() is executed by default.

    2) afterSetup() invokes method build()

    3) method build() verifies whether it is required to render any side panels or bottom (relationship panels) in addition to the record panel ("middle") and then invokes method createMiddleView()

    4) createMiddleView() halts the program flow until the "middle" view is actually crated, then invokes method getGridLayout() and provides as a parameter for it, a callback function that will create a view, extended from the view class client/src/views/record/detail-middle.js which will actually render the record (entity) panels and fields.

    However in order to be able to create the "middle" view above, the information resulting from the execution of getGridLayout() is necessary, and that is why the program execution has to be halted and the instructions to build the "middle" view are encapsulated in a callback function which will be executed after getGridLayout() is done.

    5) getGridLayout() invokes the helper class layoutManager to fetch the entity's layout information (panels and fields layout) stored as a json file (see post above regarding how the layout json files are created and updated manually through the Layout Manager Administration facility) and then provides this data to the method convertDetailLayout()

    6) convertDetailLayout() takes the layout json data (simple layout) , which describes which panels to render and which fields inside those panels to render, and builds a complex object that inserts a field view class for each of the fields specified in the simple layout.

    As an analogy, one can think of the simple layout, which is manipulated through the Layout Manager Admin facility, and the layout for a house that tells you how many rooms the house has and how are distributed and the results provided by convertDetailLayout() as the specifications of how to build each of the rooms.

    7) convertDetailLayout() returns the complete layout data back to getGridLayout() which then executes the callback instruction created at createMiddleView() (number 4 above) to actually render the record (entity) in the "middle" section of the display.
    Last edited by telecastg; 02-02-2022, 02:36 AM.

    Leave a comment:


  • item
    commented on 's reply
    Hello @telecastg,
    i write here because i think it's here

    out-of-box, we have layout "listForAccount" and "listForContact".. it's for bottom panel,
    where we can create this kind of layout for custom entity ? witch files to modify ?
    sample :
    Patient -> bottomPanel = contacts => listForPatient
    Institution -> bottomPanel => contacts => listForInstitution

    so i need bottomPanel to have differents field if it's Patient or Institution.

    Regards

  • telecastg
    replied
    How is Layout data retrieved by the Layout Manager ?

    Layout data is stored in json files located under the affected entity's module namespace.

    For example for the "Call" entity, which is defined under the module "Crm", the various layout files (one for each type, like "detail", "detailSmall", "list", "listSmall", etc) are located inside the directory application/Espo/Modules/Crm/Resources/layouts/Call/

    Therefore the detail layout data is stored in the file application/Espo/Modules/Crm/Resources/layouts/Call/detail.json

    Click image for larger version  Name:	Screenshot 2022-01-16 112940.png Views:	0 Size:	114.9 KB ID:	78281


    When a User clicks on the Administration > Layout Manager > Calls > Detail menu option the following actions take place inside Espo

    1) Front end script client/src/layout-manager.js looks for the layout data in cache and if not found, makes an API (Ajax get request) call: Call/layout/detail

    2) Json script application/Espo/Resources/routes.json interprets the instruction and invokes method getActionRead() at Controller class application/Espo/Controllers/Layout.php providing two parameters: $scope = "Call", $name = "detail".

    3) Controller class application/Espo/Controllers/Layout.php invokes method getForFrontend($scope, $name) at Service class application/Espo/Services/Layout.php

    4) Service class application/Espo/Services/Layout.php checks permissions and team membership to determine if the user request requires the retrieval of a specific layout file or the default for all users. If a specific layout is required the Service class invokes method get($scope, $name) at Utils class application/Espo/Core/Utils/Layout.php , otherwise invokes method getDefault($scope, $name) at the same Utils class.

    5) Utils class application/Espo/Core/Utils/Layout.php determines the correct path where the requested layout file exists, reads the file and returns the data as a JSON object to the Service class which passes the same to the Controller class which then passes it to the front end script.
    Last edited by telecastg; 01-16-2022, 08:39 PM.

    Leave a comment:


  • telecastg
    replied
    How are layouts used to render an entity detail display:

    Layouts are json files created through the Administration > Layout Manager facility and tell Espo how to order and render the entity's fields in different display modes.

    For the "detail" display mode, a record (entity) is rendered in one or more panels, each panel containing the fields and organization of these fields specified in the layout file like this:
    Click image for larger version  Name:	Layout Manager.jpg Views:	0 Size:	61.3 KB ID:	78108

    If an entity does not have any detail layout associated with it, like in the case of a new custom entity, Espo will assume a detail layout that contains only the "name" attribute occupying one half of the single row.

    The above information is referred to as "simpleLayout" in the client/src/views/record/detail.js class.

    The "simpleLayout" is processed by the above class through the method getGridLayout() to create a complete specification of not just which fields are displayed where but also which field view classes are to be used to render each field in detail or edit modes, the document element where each field is to be rendered and the model providing the data bound to each field.

    The client/src/views/record/detail.js class takes then the output from the method getGridLayout() and uses it to render the record (entity) in the "middleView" class which is rendered in the "middle" html div where the record's details are displayed in the screen.

    Click image for larger version  Name:	Middle View.png Views:	0 Size:	73.6 KB ID:	78109
    Last edited by telecastg; 01-11-2022, 10:54 PM.

    Leave a comment:


  • telecastg
    replied
    How to modify in an "upgrade safe" way the script views/record/search.js

    Click image for larger version

Name:	Capture.PNG
Views:	1200
Size:	4.5 KB
ID:	70787


    Check this post for detailed instructions to incorporate custom code changes to the search class without changing any core scripts
    I want to customize espocrm/client/src/views/record/search.js file. Can anyone help me with how to do this? Research: We have espocrm/client/src/views/list.js file which will define the searchView as views/record/search. Similarly, the recordView: 'views/record/list' is defined in theespocrm/client/src/views/list.js. I


    Leave a comment:


  • telecastg
    replied
    What is the program flow for an API call ?

    API calls invoke a back end controller which generally then calls for a service class to execute the requested action.

    API call > router > back end controller > service class

    To see an example check this post. https://forum.espocrm.com/forum/deve...3408#post63408

    Leave a comment:


  • telecastg
    commented on 's reply
    Thanks tothewine :-)
Working...
X