Aikau Mini Examples - Role Based Rendering

cancel
Showing results for 
Search instead for 
Did you mean: 

Aikau Mini Examples - Role Based Rendering

ddraper
Intermediate II
0 11 5,866

Introduction

This is one post in a series of short examples of things that can be done using the Aikau framework. The series is not intended to provide complete documentation but simply to show how to solve frequently encountered problems, implement repeating UI patterns, show case how to use existing widgets or how to create new ones.

Real World Use Case

One of the most frequently asked questions we seem to be getting lately is whether or not we can render a different UI depending upon what groups the logged in user is a member of (also known as “role based rendering”).

This can range from simply displaying a warning instead of the page contents if the current user doesn’t have the privileges to be there, right up to fine grained differences between what different users see on the page.

Example

All widgets can be configured with a set of “renderFilter” rules that determine whether or not the widget is rendered (note: rendered not visible - if the renderFilter does not pass the widget will not be created). The rules are not constrained to just groups, it’s possible to define rules for any property that the widget has access to. However, two key pieces of information are always cascaded through the widget hierarchy and these are “groupMemberships” and “currentItem”.

This will be quite a convoluted example (aren’t they all?) but should at least show the options available for configuring renderFilters.

Pre-Requisites

First of all, log in as the Administrator and create a new group with the identifier “EXAMPLE” (under the covers Alfresco will actually call it “GROUP_EXAMPLE”). Then create two new users and add one (not both) to the group.

Create Warning Banner

In the JavaScript controller for the page define a warning widget that should be displayed if the logged in user is neither an Administrator or a member of the new example group.

var warning = {
  name: 'alfresco/header/Warning',
  config: {
    renderFilterMethod: 'ALL',
    renderFilter: [
      {
        target: 'groupMemberships',
        property: 'GROUP_ALFRESCO_ADMINISTRATORS',
        renderOnAbsentProperty: true,
        values: [false]
      },
      {
        target: 'groupMemberships',
        property: 'GROUP_EXAMPLE',
        renderOnAbsentProperty: true,
        values: [false]
      }
    ],
    warnings: [
      {
        message: 'You aren't in right group',
        level: 3
      }
    ]
  }
};
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍


The “renderFilterMethod” defines whether to use AND or OR logic for the rules (e.g. ALL rules must pass in order for the widget to be rendered or ANY one of the rules must pass). There are two rules defined, one for the main Administrators group and one for the our newly created group. The “target” is set to be “groupMemberships” (one of the previously mentioned key attributes) and we also declare set “renderOnAbsentProperty” to be true so that the rule is passed if the group doesn’t appear in the “groupMemberships” object. Finally we set an array of values that the target property must be in order for the rule to be satisfied.

Create Menu Bar

Now define a menu bar that will be displayed if the user is an Administrator or a member of the new group.

var menuBar = {
  name: 'alfresco/menus/AlfMenuBar',
  config: {
    renderFilterMethod: 'ANY',
    renderFilter: [
      {
        target: 'groupMemberships',
        property: 'GROUP_ALFRESCO_ADMINISTRATORS',
        values: [true]
      },
      {
        target: 'groupMemberships',
        property: 'GROUP_EXAMPLE',
        values: [true]
      }
    ],
    widgets: []
  }
};

This time we are inverting the rules. The “renderFilterMethod” is set to ANY as they user just needs to be a member of one of the groups (although being a member of both groups is also fine). The “renderOnAbsentProperty” attribute is omitted because we want to be sure the user is a member of the group and the target value is switched from false to true.

Current Item Data

We’ll now define a custom “currentItem” object for our menu bar:

menuBar.config.currentItem = {
  test: {
    value: 'show'
  }
};‍‍‍‍‍

This data can then be used to determine whether or not to render a couple of menu bar items:

var menuBarItems = [
  {
    name: 'alfresco/menus/AlfMenuBarItem',
    config: {
      label: 'Should Appear',
        renderFilter: [
          {
            property: 'test.value',
            values: ['show']
          }
        ]
      }
    },
    {
      name: 'alfresco/menus/AlfMenuBarItem',
      config: {
        label: 'Should NOT appear',
        renderFilter: [
          {
            property: 'test.value',
            values: ['visible']
          }
        ]
      }
    }
];‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
menuBar.config.widgets = menuBarItems;

We’re able to omit the “target” attribute because the default behaviour is to check the “currentItem” object. This also shows how we can use dot-notation to address nested attributes in the “currentItem” object.

The first menu bar item should be displayed (because test.value is “show”) but the second should not be rendered (because test.value is not “visible”).

Build the Page

Finally let’s combine all the definitions into the JSON model for the page:

model.jsonModel = {
  widgets: [
    {
      name: 'alfresco/layout/VerticalWidgets',
      config: {
        widgets: [ warning, menuBar ]
      }
    }
  ]
};

model.jsonModel.groupMemberships = user.properties['alfUserGroups'];

The last line is the most important - we need to ensure that the “alfUserGroups” property is retrieved from the current user data (available in the user object since Alfresco 5.0).

Example In Action

This is defined in a JavaScript controller for a WebScript mapped to the /renderFilter URL. When deployed to a Share (or any Surf based Aikau application) this can be accessed by the URL: http://localhost:8081/share/page/dp/ws/renderFilter

These screen shots show the two states depending upon which user has logged in:

 

AikauEx4_groupfail

AIkauEx4_menu_bar_item

Download the entire example here.

Download a PDF version of this document here.

11 Comments
blog_commenter
Active Member
Can we use targetId atrribute in renderfilter rule like  targetId atrribute is used in visibilityConfig rules to assign other widgets fieldId and value?  If not, how to make use of other widgets fieldId and value in current widget renderfilter rule?
ddraper
Intermediate II
@Kumar You can use 'visibilityConfig' on regular widgets (see https://www.alfresco.com/blogs/developer/2014/10/28/aikau-updates-to-dynamic-visibility-config/) however, please note that this is *dynamic* visibility (i.e. it can switch between being visible and invisible without reloading the page) and that the widget DOM will always be present in the HTML source.



The purpose of renderFilter is to actually prevent the creation of the widget at all (i.e. it will have no representation in the DOM and can never be made visible).



You should also be careful not to get confused between 'fieldId' which is a form control specific attribute and 'id' which can be used on any widget.
blog_commenter
Active Member
Thanks for the reply. actually we need to prevent the creation of the widget dynamically depending on other widget value(forms/controls/select selectOption value). Is it possible to render widget dynamically?
ddraper
Intermediate II
If you're defining your model in a WebScript then you could programmatically control whether or not the widget is displayed using code in the JS controller. If the data is available in the currentItem of a widget then you can use that. If you're creating a payload that is published to create a dialog used for editing an item in a list then you should control that using a PROCESS type of payload (see the PublishPayloadMixin module and examples in the tutorial). It's very hard to give you a definitive answer without seeing your implementation... but in general the answer would be, yes... you  can.
blog_commenter
Active Member
Thanks for reply.

This is the Action we are using for custom datalist edit functionality:

{

                  name: 'alfresco/renderers/PublishAction',

                  config: {

                 iconClass: 'edit-16',    // image

                      additionalCssClasses: 'call-to-action',              

                      publishTopic: 'ALF_CREATE_FORM_DIALOG_REQUEST',      

                      publishPayloadType: 'PROCESS',

                      publishGlobal: true,

                      publishPayloadModifiers: ['processCurrentItemTokens', 'convertNodeRefToUrl'],

                      publishPayload: {

                          dialogTitle: 'Edit ' + dataListTypes[i].title + ' Item',

                          dialogConfirmationButtonTitle: 'Update',

                          dialogCancellationButtonTitle: 'Cancel',

                          formSubmissionTopic: 'ALF_CRUD_CREATE',          

                          formSubmissionPayloadMixin: {

                           url: 'api/node/{nodeRef}/formprocessor',

                           pubSubScope: pubSubScope,

                          },

                          widgets: editPropertiesAndAssociations  // variable includes the forms controls

                      }

                  }

                }

and I adding  forms/controls/widgets to variable like this :

editPropertiesAndAssociations.push(

             {

              id: association.title + 'Added',

              name: 'alfresco/forms/controls/DojoSelect',

              config: {

               fieldId: association.title + 'Added',

               label: association.title,

               name: 'assoc_' + association.name.replace('essg:', 'essg_') + '_added',

               value: null,

               description: associationDisplayValue,

               optionsConfig: {

                publishTopic: 'ALF_GET_FORM_CONTROL_OPTIONS',

                publishPayload: {

                 url: url.context + '/proxy/alfresco/slingshot/editDataListItems/' + siteId + '/datalists/'

                  + containerName + '/' + associationDisplayValue + '/' + associationNoderef,

                 itemsAttribute: 'datalists',

                 labelAttribute: 'name',

                 valueAttribute: 'nodeRef' //edited association nodeRef

                }

               }

              }

             },

             {

              name: 'alfresco/forms/controls/DojoSelect',

              config: {

               fieldId: association.title + 'Removed',

               name: 'assoc_' + association.name.replace('essg:', 'essg_') + '_removed',

               value: null,

               renderFilter: [{

                target: '',      // we need to use the widget (id: association.title + 'Added')

     property: 'value',

     renderOnAbsentProperty: true,

     values: ['no']

               }],

               optionsConfig: {

                publishTopic: 'ALF_GET_FORM_CONTROL_OPTIONS',

                publishPayload: {

                 url: url.context + '/proxy/alfresco/slingshot/getDataListItems/' + siteId + '/datalists/'

                  + containerName + '/' + associationDisplayValue + '/' + associationNoderef,

                 itemsAttribute: 'datalists',

                 labelAttribute: 'name',

                 valueAttribute: 'nodeRef' // already existing association nodeRef

                }

               }

              }

             });

is this the correct way.
ddraper
Intermediate II
It depends on what is in your editPropertiesAndAssociations variable... that's where you can control if the widget is displayed.
blog_commenter
Active Member
Thanks again. sorry for asking you this again. RenderFiletr target atrribute will take currentItem as default. In this case editPropertiesAndAssociations variable is the currentItem that I need to use in renderFilter. If no what will be the currentItem.
ddraper
Intermediate II
No, it's not. The currentItem attribute is automatically set when iterating over data (e.g. in an alfresco/lists/AlflList) otherwise you need to manually configure it.
blog_commenter
Active Member
Thanks for reply, actually I want to render form control widget dynamically. I am not using visibility config here. I have 2 DojoValidationTextBox form control widgets in dialog form and I need to render second DojoValidationTextBox if the first one default value is edited by user. I read some renderFilter concepts but I am not able to fix this. here is my code:



var button = {

             name: 'alfresco/buttons/AlfDynamicPayloadButton',

             config: {

                 label: 'New ' + dataListTypes[i].title + ' Item',

                 additionalCssClasses: 'call-to-action',              

                 publishTopic: 'ALF_CREATE_FORM_DIALOG_REQUEST',      

                 publishPayloadType: 'PROCESS',

                 publishPayloadModifiers: ['processCurrentItemTokens'],

                  publishPayload: {

                          dialogTitle: 'Edit ' + dataListTypes[i].title + ' Item',

                          dialogConfirmationButtonTitle: 'Update',

                          dialogCancellationButtonTitle: 'Cancel',

                          formSubmissionTopic: 'ALF_CRUD_CREATE',          

                          formSubmissionPayloadMixin: {

                           url: 'api/node/{nodeRef}/formprocessor',

                           pubSubScope: pubSubScope,

                          },

                          widgets: editPropertiesAndAssociations

                      }

         };

editPropertiesAndAssociations.push( {

                                fieldId: association.title + 'Added',

                  name: 'alfresco/forms/controls/DojoValidationTextBox',

                  config: {

                   name: 'assoc_' + association.name.replace('essg:', 'essg_') + '_added',

                   value: associationNoderef

                    }

              },

                       {

                                fieldId: association.title + 'Removed',

                  name: 'alfresco/forms/controls/DojoValidationTextBox',

                  config: {

                   name: 'assoc_' + association.name.replace('essg:', 'essg_') + '_removed',

                   value: associationNoderef

                    }

                       }

how can I render the widget(fieldId: association.title + 'Removed') dynamically if user enter value to the widget(fieldId: association.title + 'Added') other than default value.
blog_commenter
Active Member
Hi Dave,



Can we render the actions popup based on the site role. For example I need to remove download-action from search page based on the site role assigned to the user.



Thanks & Regards,

Anooj Agarwal.
ddraper
Intermediate II
@Anooj - in principal yes, however I don't think that you'll have the user site membership readily to hand. You might be better relying on the permissions metadata for each node. This might be especially relevant as it is possible to override permissions for specific nodes for specific users (so you could deny read permission to any member of the site, regardless of their role).