Extjs Security plugin

An Extjs plugin that hides/disables a component based on the user roles provided and user roles required for a component

/**
* Hides/Disables a component based on the user roles provided and user roles required for a component
* Example usage :
* 1. Default behavior to prevent rendering of component if required role is not present. Suitable for small components like button, textfields and other form controls
* plugins: [
* {
* ptype: 'security',
* roles: ['ROLE_ADMIN', 'ROLE_SUBADMIN', 'APP_ROLE1'],
* }
* ]
*
* 2. Hide of component if required role is not present. Suitable for small components like button, textfields and other form controls
* plugins: [
* {
* ptype: 'security',
* hideComponent: true,
* roles: ['ROLE_ADMIN', 'ROLE_SUBADMIN', 'APP_ROLE1'],
* }
* ]
*
* Configs :
* roles => List of roles which are required for this component
* disableComponent => Boolean (Optional) : If true, disables the component instead of preventing render
* hideComponent => Boolean (Optional) : If true, component is present in dom but hidden
* failureCallback => Function (Optional) : Callback function to be called if required role is not present
*
* Note :
* => For panel with header buttons, tabpanels and similar card based views, please use hideComponent: true config and implement logic to switch to correct card to achieve consistent behavior
* => disableComponent config might not behave properly for containers
*/
Ext.define('App.common.Security', {
extend: 'Ext.AbstractPlugin',
alias: [
'plugin.security'
],
userRoles: [],
roles: [],
disableComponent: false,
init: function (component) {
// init defaults
this.disableComponent = this.disableComponent || false;
this.userRoles = User.getRoles() || [];
this.roles = this.roles || [];
this.failureCallback = this.failureCallback || function (component) { };
// check access
var hasAccess = this.userHasAccess();
if (!hasAccess) {
if (this.disableComponent) {
component.disable();
this.failureCallback(component);
return;
}
// hide if no access
if (this.hideComponent) {
component.hide();
this.failureCallback(component);
return;
}
// default prevent render
component.on({
beforerender: function () {
this.failureCallback(component);
return false;
},
scope: this
});
}
},
userHasAccess: function () {
var userRoles = this.userRoles,
roles = this.roles;
if (userRoles.length === 0 && roles.length > 0) {
return false;
}
if (userRoles.length === 0 && roles.length == 0) {
return true;
}
var matchingRoles = roles.filter(function (role) {
return Ext.Array.contains(this.userRoles, role);
}, this);
return matchingRoles.length > 0;
}
});

Extjs I18n (Keys & Translations loaded from remote endpoint)

This is similar to previous post, only difference is that the keys and translations are loaded from a remote source.

For an enterprise level app, its not a good idea to store huge list of keys & translations inside the app itself because it will become unmaintainable at some point and moreover every change would require redeploy of the app. So ideally there should be a remote store where we can store and manage these keys and their corresponding translations in all languages. Below example illustrates how we can fetch I18n keys from a remote endpoint and store them in an in-memory store that can be loaded once at app load and then used for all the translations.

Ext.define('App.common.locale.I18n', {
extend: 'Ext.Base',
singleton: true,
alternateClassName: ['I18n'],
requires: [
'App.store.locale.Translations', // in memory store to keep translations
'App.api.config.Endpoints', // config file with endpoint information
],
initLocale: function (language) {
// STEP 1. find which language to load
var pageParameters = Ext.urlDecode(window.location.search.substring(1));
if (language) {
this.locale = language;
localStorage.setItem('userLanguage', language);
} else if (pageParameters.lang && (pageParameters.lang === 'en' || pageParameters.lang === 'de' || pageParameters.lang === 'fr' || pageParameters.lang === 'it')) {
// check in url
localStorage.setItem('userLanguage', pageParameters.lang);
this.locale = pageParameters.lang;
} else {
// check in localStorage
var storedLocale = localStorage.getItem('userLanguage');
this.locale = storedLocale ? storedLocale : 'en';
}
// STEP 2. Load translations
// initialize translations from remote endpoint based on the language
var translationStore = Ext.getStore('Translations');
translationStore = translationStore || Ext.create('App.store.locale.Translations');
var translations = this.doGetSynchronous(this.locale);
translationStore.setProxy({ type: "memory", data: translations }).load();
this.initialized = true;
},
get: function (key) {
if (!this.initialized) {
this.initLocale();
}
if (!key) {
return '';
}
var store = Ext.getStore('Translations'),
record = null;
record = store.findRecord('key', key);
if (record) {
return record.get('value');
} else {
console.warn('No translation found for key ' + key);
return key;
}
},
locale: null,
initialized: false,
changeLanguage: function (language) {
var newURL = window.location.pathname;
// clean URL
window.history.pushState("App", "App", newURL);
this.initLocale(language);
window.location.reload();
},
doGetSynchronous: function (language) {
var response = null;
Ext.Ajax.request({
url: Endpoints.getUrl(Endpoints.urls.translations) + '/' + language,
async: false,
success: function (data) {
var data = data || {};
response = Ext.decode(data.responseText, true);
},
failure: function (error) {
response = error;
}
});
return response;
}
});
view raw I18n.js hosted with ❤ by GitHub
Ext.define('App.view.authentication.Login', {
extend: 'Ext.container.Container',
xtype: 'login-view',
items : [
{
xtype: 'formpanel',
title : I18n.get('Login.Title.Text')
}
]
});
view raw Login.js hosted with ❤ by GitHub
[
{
"lang": "en",
"key": "Login.Title.Text",
"value" : "Welcome to your Account"
}
]
Ext.define('App.store.locale.Translations', {
extend: 'Ext.data.Store',
alias: 'store.translations',
storeId : 'Translations',
fields: [ 'lang', 'key', 'value'],
proxy: {
type: 'memory',
reader: {
type: 'json'
}
}
});

Extjs I18n (Keys & Translations stored in application itself)

ExtJs components does support internalization but for the other content present in component (titles, labels, error messages etc.) we often need to have a utility which can be used to translate the keys to their corresponding translations based on the selected language. Below is one way of creating such a utility which works based on the keys and translations stored inside the app that can be used for adding I18n keys for texts where translations are required.

Ext.define('App.common.I18n', {
singleton: true,
alternateClassName: ['I18n'],
requires: [
'App.locale.locale-de',
'App.locale.locale-en',
'App.locale.locale-fr',
'App.locale.locale-it'
],
initLocale: function () {
var pageParameters = Ext.urlDecode(window.location.search.substring(1));
// check in url
if (pageParameters.lang && (pageParameters.lang === 'en' || pageParameters.lang === 'de' || pageParameters.lang === 'fr' || pageParameters.lang === 'it')) {
localStorage.setItem('userLanguage', pageParameters.lang);
this.locale = pageParameters.lang;
} else {
// check in localStorage
var storedLocale = localStorage.getItem('userLanguage');
this.locale = storedLocale ? storedLocale : 'en';
}
},
get: function (key) {
if (!this.locale) {
this.initLocale();
}
switch (this.locale) {
case 'en': {
return EN.Labels[key] ? EN.Labels[key] : key;
}
case 'de': {
return DE.Labels[key] ? DE.Labels[key] : key;
}
case 'fr': {
return FR.Labels[key] ? FR.Labels[key] : key;
}
case 'it': {
return IT.Labels[key] ? IT.Labels[key] : key;
}
default: return EN.Labels[key] ? EN.Labels[key] : key;
}
},
getYesNo: function (value) {
return value === true ? this.get('Common.Label.Yes') : this.get('Common.Label.No');
},
getYesNoInternet: function (value) {
return value === true ? this.get('Common.Label.YesInternet') : this.get('Common.Label.NoInternet');
},
getOnOff: function (value) {
return value === true ? this.get('Common.Label.On') : this.get('Common.Label.Off');
},
getOnOffNotPossible: function (value) {
if (!value) {
return value;
}
if (value === 'ON') {
return this.get('Common.Label.On');
} else if (value === 'OFF') {
return this.get('Common.Label.Off');
}
return this.get('Common.Label.NotPossible');
},
locale: null
});
view raw I18n.js hosted with ❤ by GitHub
Ext.define('App.locale.locale-en', {
singleton: true,
alternateClassName: ['EN'],
constructor: function (config) {
this.initConfig(config);
},
Labels: {
'Common.Label.Yes': 'Yes',
'Common.Label.No': 'No',
'Common.Label.Off': 'Off',
'Common.Label.On': 'On',
'Common.Label.NotPossible': 'Not possible',
}
});
.