Best way to hide/disable GUI elements based on use

2020-05-19 02:39发布

问题:

I am starting a web application with client side implemented in pure ExtJS and middle tier in Grails. The application has role-based authorization, where a user can have many fine grained roles like SOME_FORM_READ, SOME_FORM_UPDATE, SOME_DATA_DELETE, SOME_DATA_READ, etc. Based on the roles of the user, certain GUI elements need to be disabled or hidden, while others need to be in a read-only mode.

I did some search on the web, but didn't find any design pattern that specifically addresses this issue, so I came up with my own design. I am sure that a lot of the web applications out there will have a similar requirement, so I'd like to post my design here and hear people's opinion on it. By no means is my design a perfect one, but I hope it can be improved with everyone's input. Although I am working with ExtJS, the general design should also apply to similar frameworks like GWT, Flex, Swing, etc.

So, here it goes:

There are four types of code (or information) we need to deal with in the client tier regarding authorization:

  1. GUI element manipulation code, for example:

    panel.hide() form.setReadOnly(true)

  2. GUI element permission requirement, for example:

    form.requires('READ', 'FORM_READ_ROLE')

    adminPanel.requires('ADMIN_ROLE')

  3. User privilege information, which is basically a list of roles that the user has;

  4. Authorization logic: determines which elements to hide/disable based on user privilege;

The core of the design is a singleton, named GUIPermissionManager, or GPM for short. This is a centralized design in that most of the code is in GPM, so that GUI elements are not polluted by the authorization code. This is how GPM works:

  • GUI elements (that need certain permission to access) register their permission information with GPM, like this:

    GPM.register(this, 'DEPARTMENT_DELETE_ROLE'); // button for deleting a department

  • GPM maintains a list of GUI permission registration

  • On user login, GPM receives the list of roles the use is assigned

  • GPM walks through the GUI permission registration list and based on user privilege, determines which part of the GUI to hide, and in turn, calls element.hide() accordingly

Questions:

  • GUI elements are organized in a tree hierarchy, e.g. a panel contains a button bar and a form, so when the panel is hidden, there is no need to check further if the button bar and the form need to be hidden. Problem: how to register and maintain this hierarchical information in GPM?
  • Currently, I can only think of two use cases for GUI element: hide an element or set an element as read-only (such as a form). Is there any other use cases?
  • In ExtJS, to hide an element, we call hide(), but to set a form read-only, we have to come up with our own function, let's say it's called setReadOnly(), how to let GPM know which function to call? Passing the function as part of the registration?
  • What is the best way to set a form read-only? If I extend the form component with the setReadOnly() functionality, there will be a lot of code duplication and I have to do this for every form that need permission control. Is it possible to create a dynamic form transformer in GPM so that if a form is set to read-only, it automatically replaces all editable fields with display-only fields?

回答1:

Q1: Hierarchical UI element hiding - Optimizing your GPM to avoid hiding elements that are already hidden via a parent is not going to have much of a performance boost in my opinion. My reasons:

  1. You load permissions once when a user logs in, not all the time.
  2. Depending on how it is coded, additional processing will be needed to determine the hierarchy anyways.
  3. With adequate planning you could avoid registering dozens and dozens of components and stick to overarching containers.

If you really want to keep track of hierarchical information you can always use the 'contains' method that all container components provide for checking if a DisplayObject is contained anywhere in its child list (including down the chain). This could be called up each time a component is registered to check if it already has a registered parent.

A flag could then be set in a dictionary to ignore hiding on that component. This flag could get checked first while iterating over the list of registered components to determine what should be hidden. The dictionary could use keys that correspond to the registered component's UID. Furthermore this flag could be used to ignore the component when it comes time to ignore other GPM functions, like form disabling (as the form would never be seen anyways).

Q2. Off the top of my head you could disable/enable components, implement state changes, or intercept events, and all alerts. This is really too broad of a question as anything could be done - really up to the designer.

Q3. You could:

  1. Provide parameters when registering your components, such as to indicate what type they are (container for hiding, form for setting to read only, etc.)
  2. Check each component as it is registered to determine what is going to be done with it.

You would essentially be establishing a contract with various components where the GPM is aware of their interfaces and interacts with them accordingly.

Q4. You can always set a form to be disabled (enabled = false). This prevents any user interaction. Some skins will change to indicate that the components are disabled so you may want to modify their skins to prevent some of this display behavior. On that line, you could also change their skins to hide certain elements such as the TextInput box's border so as to make it look more like a 'view' than a disabled input.

It would be possible to create a 'transformer' that changes TextInputs with RichText components and such. This would take a decent amount of work and should probably be built into an extended Form class instead of the GPM. I think different skins states for each component type may be a better solution, so as to avoid creation and destruction of components just to change how the form appears.



回答2:

A note of caution! User priviledges/authorization should be controlled on the server side and not on the client side especially for js based webapps. Its a big security risk. You should consider that in your framework.

edit - what is your framework for client side auth/priv management? That will mostly direct your client side GUI management.