Use Case:
We have a "Tenancy" entity that is related to a "CollectionIssue" entity in a one to many relationship.
We want to display the "CollectionIssues" in a side panel when viewing a single "Tenancy" record similar to how "Tasks" are displayed.

Step 1:
Specify in the "Tenancy" clientDefs script, that we want to have a side panel that will contain a list of "CollectionIssues"
custom/Espo/Custom/Resources/metadata/clientDefs/Tenancy.json
	Step 2:
Create the view class that will display the list of "CollectionIssues" in the side panel (specified in the script above)
client/custom/src/views/side-panels/collection-issues.js
	Step 3:
Create the custom template to display the list of CollectionIssues as a side panel
client/custom/res/templates/record/panels/collection-issues.tpl
	Step 4:
Clear cache and rebuild
					We have a "Tenancy" entity that is related to a "CollectionIssue" entity in a one to many relationship.
We want to display the "CollectionIssues" in a side panel when viewing a single "Tenancy" record similar to how "Tasks" are displayed.
Step 1:
Specify in the "Tenancy" clientDefs script, that we want to have a side panel that will contain a list of "CollectionIssues"
custom/Espo/Custom/Resources/metadata/clientDefs/Tenancy.json
Code:
	 "sidePanels": {
    "detail": [
        {
            "name": "collectionIssues",
            "label": "Collection Issues",
            "view": "custom:views/side-panels/collection-issues",
            "aclScope": "CollectionIssue"
        },
    ]
},
Create the view class that will display the list of "CollectionIssues" in the side panel (specified in the script above)
client/custom/src/views/side-panels/collection-issues.js
Code:
	define('custom:views/side-panels/collection-issues', 'views/record/panels/relationship', function (Dep) {
    return Dep.extend({
    name: 'collection-issues',
    template: 'custom:record/panels/collection-issues',
    tabList: ['open', 'closed'],
    defaultTab: 'open',
    sortBy: 'createdAt',
    asc: false,
    buttonList: [
        {
            action: 'createCollectionIssue',
            title: 'Create Collection Notice',
            acl: 'create',
            aclScope: 'CollectionIssue',
            html: ' < span class="glyphicon glyphicon-plus" > < /span >'
        }
    ],
    listLayout: {
        rows: [
            [
                {
                    name: 'name',
                    link: true,
                }
            ],
            [
                {name: 'createdAt'},
                {name: 'totalAmountDemanded'},
                {name: 'status'},
            ]
        ]
    },
    events: _.extend({
        'click button.tab-switcher': function (e) {
            var $target = $(e.currentTarget);
            this.$el.find('button.tab-switcher').removeClass('active');
            $target.addClass('active');
            this.currentTab = $target.data('tab');
            this.collection.where = this.where = [
                {
                    type: 'primary',
                   value: this.currentTab
                }
            ];
            this.listenToOnce(this.collection, 'sync', function () {
                this.notify(false);
            }.bind(this));
            this.notify('Loading...');
            this.collection.fetch();
            this.getStorage().set('state', this.getStorageKey(), this.currentTab);
        }
    }, Dep.prototype.events),
    data: function () {
        return {
            currentTab: this.currentTab,
            tabList: this.tabList
        };
    },
    getStorageKey: function () {
        return 'collectionIssues-' + this.model.name + '-' + this.name;
    },
    setup: function () {
        this.scope = this.model.name;
        this.link = 'collectionIssues';
        this.currentTab = this.getStorage().get('state', this.getStorageKey()) || this.defaultTab;
        this.where = [
            {
                type: 'primary',
                value: this.currentTab
            }
        ];
    },
    afterRender: function () {
        var url = this.model.name + '/' + this.model.id + '/' + this.link;
        if (!this.getAcl().check('CollectionIssue', 'read')) {
            this.$el.find('.list-container').html(this.translate('No Access'));
            this.$el.find('.button-container').remove();
            return;
        };
        this.getCollectionFactory().create('CollectionIssue', function (collection) {
            this.collection = collection;
            collection.seeds = this.seeds;
            collection.url = url;
            collection.where = this.where;
            collection.sortBy = this.sortBy;
            collection.asc = this.asc;
            collection.maxSize = this.getConfig().get('recordsPerPageSmall') || 3;
            var rowActionsView = '';
            this.listenToOnce(this.collection, 'sync', function () {
                this.createView('list', 'views/record/list-expanded', {
                    el: this.getSelector() + '  > .list-container',
                    pagination: false,
                    type: 'listRelationship',
                    rowActionsView: rowActionsView,
                    checkboxes: false,
                    collection: collection,
                    listLayout: this.listLayout,
                }, function (view) {
                    view.render();
                });
            }.bind(this));
            this.collection.fetch();
        }, this);
    },
    actionCreateCollectionIssue: function (data) {
        var self = this;
        var link = this.link;
        var scope = 'CollectionIssue';
        var foreignLink = this.model.defs['links'][link].foreign;
        this.notify('Loading...');
        var viewName = this.getMetadata().get('clientDefs.' + scope + '.modalViews.edit') || 'views/modals/edit';
        this.createView('quickCreate', viewName, {
            scope: scope,
            relate: {
                model: this.model,
                link: foreignLink,
            }
        }, function (newView) {
            newView.render();
            newView.notify(false);
            this.listenToOnce(newView, 'after:save', function () {
                this.collection.fetch();
                this.model.trigger('after:relate');
            }, this);
        });
    },
    actionRefresh: function () {
        this.collection.fetch();
    },
    actionClose: function (data) {
        var id = data.id;
        if (!id) {
            return;
        }
        var model = this.collection.get(id);
        model.save({
            status: 'Closed'
        },
        {
            patch: true,
            success: function () {
                this.collection.fetch();
            }.bind(this)
         });
    },
});
});
Create the custom template to display the list of CollectionIssues as a side panel
client/custom/res/templates/record/panels/collection-issues.tpl
Code:
	< div class="btn-group button-container" >
    {{#each tabList}}
        < button class="btn btn-default all{{#ifEqual ../currentTab this}} active{{/ifEqual}} tab-switcher" data-tab="{{./this}}" > {{translate this scope='CollectionIssue' category='presetFilters'}} < /button >
    {{/each}}
< /div >
< div class="list-container small" >
    {{{list}}}
< /div >
Clear cache and rebuild


Comment