UI Theme Customization Design Document
Checklist
- Use Cases Reviewed
- Design reviewed
- Caveats reviewed
- Spec reviewed
- Security considerations reviewed
- Test cases reviewed
Introduction
Current CDAP UI comes with hardcoded values for primary colors, secondary colors, font family etc. These values constitute the theme of CDAP UI, and there is no easy way for users to change or customize it. To change how the UI looks, right now an user would have to modify the UI code, then rebuild the sandbox for the changes to appear. This process can be confusing and time-consuming for the user, since they may not be familiar with how the UI code is structured and may require several iterations to get the look that they want. Also, this option is only available if the user has access to CDAP code.
Goals
Provide users an easy way to change the theme of CDAP UI, without having to modify CDAP code. User can provide the customizations they want in a configuration file, and these customizations will be reflected in the UI when they start CDAP.
Use Cases
- Users can change the theme of CDAP UI, such as colors, typography, based on the customizations they provide
- Users can hide certain apps from the navbar, such as Rules Engine or Analytics, based on the customizations they provide
- Users can customize content like logo, footer text and/or footer link, based on the customizations they provide
Approach
Currently, many CDAP UI components use the same SCSS variables for their styling, so that the UI has a consistent look and feel. Changing the values of these variables will change the look of many components throughout UI.
The idea here is to expose to the users (through documentation) what these variables are, so that they can provide their own values for these variables. They can put these values in a file, and specify the name of this file/theme that they are creating in the configuration file that CDAP uses when it starts up. When CDAP's Node.js UI service starts up, it will check the name of the theme specified, fetch the corresponding file, and apply the values in this file over the default values of the SCSS variables. This will update the styling of the UI components using these SCSS variables.
As for other properties, such as logo, footer text, whether to show certain apps etc., the corresponding component in UI will check for these customizations before rendering.
The theme file's spec will be versioned, and the user has to follow the specs defined for each version to get the expected results.
Detailed Design/Implementation
For CSS styling customizations like color and font, the first thing we need to do is to change CDAP UI to use CSS variables, instead of SCSS variables. The reason for this is that the former can be edited during runtime/start-up time, but not the latter. This is because Sass is a CSS preprocessor, so UI no longer has info about these SCSS variables once they are compiled to CSS in the compilation step.
We will then define these theming CSS variables in the global scope, so that once we update these variables through Javascript, we can just update them once and have them propagate throughout the DOM tree. However, we have to be careful to never overwrite these CSS variables to different values in local scopes. Otherwise, the user would be confused when they don't see their customization applied in certain parts of UI.
Besides CSS values, the user can also customize content, such as footer text/link and logo, as well toggle features they want to see. For these values, we won't be able to just overwrite them in one place. Each UI component that can be customized will have to check whether the user has specified any related customization, and render based on that.
Next, we need to add a folder to ‘server/config’ directory, which will contain all the themes that CDAP UI can have. We will have a ‘default’ theme, as well as several other themes to start with. This folder will still be available when the sandbox is built, so users can add their own themes to it. Each theme will be a JSON file, consisting of customizable properties and their corresponding values. This JSON file will have spec version as well (e.g. 1.0, 1.1 etc.), so that we can add/modify/delete existing properties without breaking the user's customizations. The spec for a sample theme JSON file is described in the next section.
We will also add a property to 'cdap-ui/server/config/ui-settings.json, indicating the UI theme to use. By default, the value is ‘default’, corresponding to the ‘default’ theme in our themes folder. The user can change this value to point to their theme, after they have uploaded it to the themes folder.
In our Node.js server code, we already have access to the contents of 'ui-settings.json', so we just have to look for the property we used to specify the theme name. The value of this property should match the name of a theme file in the themes folder, so that we can get the file contents, and make them globally available by appending them to a global object. When CDAP UI is rendering, we can access these theme values, and update our CSS variables to use these values. Customizable components can also check this global property to conditionally render.
In the case that the UI theme property is not listed in 'ui-settings.json’, or there is no file matching the theme name in the themes folder, or there is no ‘default’ theme file (which can happen if the user accidentally deletes it), we will have fallback values for each of the customizable properties in the UI.
Caveats
I investigated whether we can update SCSS variables during runtime, as this will allow us to not have to change significant parts of our UI code to use CSS variables. However, this is not possible as mentioned above.
CSS variables feature has been implemented in almost all major browsers (with the most recent being Edge in 2017), except for IE. So we will have to investigate whether we can use polyfills to support UI theming in IE. UPDATE: Polyfill is available for IE11.
For fonts, in the short term maybe we should only allow users to choose from a specific list of fonts. This is to avoid the risk that the user chooses a font that can make the UI look broken.
Theme file sample spec
{
"spec-version": "1.0",
"styles": {
"brand-primary-color": "#ff6600",
"navbar-color": "#333333",
"font-family": "Helvetica, Arial, sans-serif"
},
"content": {
"logo": {
"type": "builtin|link|inline",
"arguments": {
"url": "http://localhost/myicon.png",
"data": "data:image/png;base64,..."
}
},
"footer-text": "Licensed under the Apache License, Version 2.0",
"footer-link": "https://www.apache.org/licenses/LICENSE-2.0"
},
"features": { "dashboard": true|false, "reports": true|false, "data-prep": true|false, "pipelines": true|false, "analytics": true|false, "rules-engine": true|false, "metadata": true|false, "hub": true|false, "footer": true|false, "ingest-data": true|false, "add-namespace": true|false }
}
Security Impact
Need to validate that the file that the user uploads to the themes folder is not malicious, especially since we are appending the file contents to the browser’s window object.
Testing Plan
Need to test the following scenarios:
UI displays correct theme when value of 'ui.theme' property in ‘ui-settings.json’ points to existing theme. The criteria for correctness is:
Customized styling is applied
Customized content is rendered
Specific features/apps show up or not, depending on the configuration
UI displays default theme when value of 'ui.theme' property in ‘ui-settings.json’ points to non-existent theme
UI displays default theme when 'ui.theme' property in ‘ui-settings.json’ does not exist
UI displays default value for a theme property in the theme file, when the value that the user provides is not valid
UI displays default value for a theme property in the theme file, when the user doesn't include that property in the file