sense-navigation
Version:
Sense Sheet Navigation + Actions visualization extension for Qlik Sense.
686 lines (655 loc) • 17.4 kB
JavaScript
/* global define */
define([
'angular',
'./lib/external/lodash/lodash.min',
'qlik',
'./lib/external/sense-extension-utils/index',
'text!./lib/data/icons-fa.json'
], function (angular, __, qlik, extHelper, iconListRaw) { // eslint-disable-line max-params
// const $injector = angular.injector(['ng']);
// const $timeout = $injector.get('$timeout');
// ****************************************************************************************
// Helper Promises
// ****************************************************************************************
/**
* Helper method to return a list of icons in a format that can be used by the
* dropdown component.
*
* @returns {Array<value,label>}
*/
function getIcons() {
const iconList = JSON.parse(iconListRaw).icons;
const propDef = [];
propDef.push({
value: '',
label: '>> No icon <<'
});
iconList.forEach(function (icon) {
propDef.push(
{
value: icon.id,
label: icon.name
}
);
});
// Can be replaced by iconList.sort
return __.sortBy(propDef, function (item) {
return item.label;
});
}
// ****************************************************************************************
// Layout
// ****************************************************************************************
const buttonStyle = {
type: 'string',
component: 'dropdown',
ref: 'props.buttonStyle',
label: 'Style',
defaultValue: 'default',
options: [
{
value: 'default',
label: 'Default'
},
{
value: 'primary',
label: 'Primary'
},
{
value: 'success',
label: 'Success'
},
{
value: 'info',
label: 'Info'
},
{
value: 'warning',
label: 'Warning'
},
{
value: 'danger',
label: 'Danger'
},
{
value: 'link',
label: 'Link'
},
{
value: 'by-expression',
label: 'Defined by expression'
},
{
value: 'by-css',
label: 'Custom style (CSS)'
}
]
};
const buttonStyleExpression = {
ref: 'props.buttonStyleExpression',
label: 'Expression to define the button style',
type: 'string',
expression: 'optional',
defaultValue: '=\'default\'',
show: function (data) {
return data.props.buttonStyle === 'by-expression';
}
};
const buttonStyleCss = {
ref: 'props.buttonStyleCss',
label: 'Expression to define button\'s CSS',
type: 'string',
expression: 'optional',
defaultValue: '=\'background-image: linear-gradient(to right, #FF512F 0%, #F09819 51%, #FF512F 100%)\'',
show: function (data) {
return data.props.buttonStyle === 'by-css';
}
};
const buttonWidth = {
type: 'boolean',
component: 'buttongroup',
label: 'Button width',
ref: 'props.fullWidth',
options: [
{
value: true,
label: 'Full Width',
tooltip: 'Button has the same width as the element.'
},
{
value: false,
label: 'Auto Width',
tooltip: 'Auto width depending on the label defined.'
}
],
defaultValue: false
};
const buttonIcons = {
type: 'string',
component: 'dropdown',
label: 'Icon',
ref: 'props.buttonIcon',
options: function () {
return getIcons();
}
};
const buttonTextAlign = {
ref: 'props.buttonTextAlign',
label: 'Label alignment',
type: 'string',
component: 'dropdown',
defaultValue: 'left',
options: [
{
value: 'center',
label: 'Center'
},
{
value: 'left',
label: 'Left'
},
{
value: 'right',
label: 'Right'
}
],
show: function (data) { /* eslint-disable-line object-shorthand */
return data.props.fullWidth;
}
};
const buttonAlignment = {
ref: 'props.buttonAlignment',
type: 'string',
component: 'dropdown',
label: 'Button position',
defaultValue: 'top-left',
options: [
{
label: 'Top left',
value: 'top-left'
},
{
label: 'Top middle',
value: 'top-middle'
},
{
label: 'Top right',
value: 'top-right'
},
{
label: 'Left middle',
value: 'left-middle'
},
{
label: 'Centered',
value: 'centered'
},
{
label: 'Right middle',
value: 'right-middle'
},
{
label: 'Bottom left',
value: 'bottom-left'
},
{
label: 'Bottom middle',
value: 'bottom-middle'
},
{
label: 'Bottom right',
value: 'bottom-right'
}
]
};
const buttonMultiLine = {
ref: 'props.isButtonMultiLine',
label: 'Multiline label',
type: 'boolean',
defaultValue: false
};
const buttonLabel = {
ref: 'props.buttonLabel',
label: 'Label',
type: 'string',
expression: 'optional',
show: function () {
return true;
},
defaultValue: 'My Button'
};
// ****************************************************************************************
// Navigation Action
// ****************************************************************************************
const navigationAction = {
ref: 'props.navigationAction',
label: 'Navigation action',
type: 'string',
component: 'dropdown',
default: 'nextSheet',
options: [
{
label: 'None',
value: 'none'
},
{
label: 'Go to first sheet',
value: 'firstSheet'
},
{
label: 'Go to next sheet',
value: 'nextSheet'
},
{
label: 'Go to previous sheet',
value: 'prevSheet'
},
{
label: 'Go to last sheet',
value: 'lastSheet'
},
{
label: 'Go to a sheet',
value: 'gotoSheet'
},
{
label: 'Go to a sheet (defined by sheet Id)',
value: 'gotoSheetById'
},
{
label: 'Go to a story',
value: 'gotoStory'
},
{
label: 'Open a website / eMail',
value: 'openWebsite'
},
{
label: 'Switch to edit mode',
value: 'switchToEdit'
}
// ,
// {
// label: "Open app",
// value: "openApp"
// }
]
};
const sheetId = {
ref: 'props.sheetId',
label: 'Sheet Id',
type: 'string',
expression: 'optional',
show: function (data) {
return data.props.navigationAction === 'gotoSheetById';
}
};
const appList = {
type: 'string',
component: 'dropdown',
label: 'Select app',
ref: 'props.selectedApp',
options: extHelper.getAppList(),
show: function (data) {
return data.props.navigationAction === 'openApp';
}
};
const sheetList = {
type: 'string',
component: 'dropdown',
label: 'Select sheet',
ref: 'props.selectedSheet',
options: extHelper.getSheetList(),
show: function (data) {
return data.props.navigationAction === 'gotoSheet';
}
};
const storyList = {
type: 'string',
component: 'dropdown',
label: 'Select story',
ref: 'props.selectedStory',
options: extHelper.getStoryList(),
show: function (data) {
return data.props.navigationAction === 'gotoStory';
}
};
const websiteUrl = {
ref: 'props.websiteUrl',
label: 'Website Url:',
type: 'string',
expression: 'optional',
show: function (data) {
return data.props.navigationAction === 'openWebsite';
}
};
const sameWindow = {
ref: 'props.sameWindow',
label: 'Open in same window',
type: 'boolean',
defaultValue: true,
show: function (data) {
return data.props.navigationAction === 'openWebsite';
}
};
// ****************************************************************************************
// Action-Group
// ****************************************************************************************
const actionOptions = [
{
value: 'applyBookmark',
label: 'Apply a bookmark',
group: 'bookmark'
},
{
value: 'clearAll',
label: 'Clear all selections',
group: 'selection'
},
{
value: 'clearOther',
label: 'Clear selections in other fields',
group: 'selection'
},
{
value: 'forward',
label: 'Move forwards (in your selections)',
group: 'selection'
},
{
value: 'back',
label: 'Move backwards (in your selections)',
group: 'selection'
},
{
value: 'clearField',
label: 'Clear selections in field',
group: 'selection'
},
{
value: 'lockAll',
label: 'Lock all selections',
group: 'selection'
},
{
value: 'lockField',
label: 'Lock a specific field',
group: 'selection'
},
{
value: 'unlockAll',
label: 'Unlock all selections',
group: 'selection'
},
{
value: 'unlockField',
label: 'Unlock a specific field',
group: 'selection'
},
{
value: 'unlockAllAndClearAll',
label: 'Unlock all and clear all',
group: 'selection'
},
{
value: 'selectField',
label: 'Select a value in a field',
group: 'selection'
},
{
value: 'selectAll',
label: 'Select all values in a field',
group: 'selection'
},
{
value: 'selectValues',
label: 'Select multiple values in a field',
group: 'selection'
},
{
value: 'selectAlternative',
label: 'Select alternatives',
group: 'selection'
},
{
value: 'selectAndLockField',
label: 'Select a value and lock field',
group: 'selection'
},
{
value: 'selectExcluded',
label: 'Select excluded',
group: 'selection'
},
{
value: 'selectPossible',
label: 'Select possible values in a field',
group: 'selection'
},
{
value: 'setVariable',
label: 'Set variable value',
group: 'variables'
},
{
value: 'toggleSelect',
label: 'Toggle field selection',
group: 'selection'
}
];
// ****************************************************************************************
// n-actions
// ****************************************************************************************
const bookmarkEnabler = ['applyBookmark'];
const fieldEnabler = ['clearField', 'clearOther', 'lockField', 'selectAll', 'selectAlternative', 'selectExcluded', 'selectField', 'selectPossible', 'selectValues', 'selectAndLockField', 'toggleSelect', 'unlockField'];
const valueEnabler = ['selectField', 'selectValues', 'setVariable', 'selectAndLockField', 'toggleSelect'];
const valueDescEnabler = ['selectValues'];
const variableEnabler = ['setVariable'];
const overwriteLockedEnabler = ['clearOther', 'selectAll', 'selectAlternative', 'selectExcluded', 'selectPossible', 'toggleSelect'];
// Just an idea for now:
// const actionGroup = {
// ref: 'actionGroup',
// label: 'Selection Action Type',
// type: 'string',
// component: 'dropdown',
// defaultValue: 'selection',
// options: [
// {
// label: 'Selection',
// value: 'selection'
// },
// {
// label: 'Bookmark',
// value: 'bookmark'
// },
// {
// label: 'Variables',
// value: 'variables'
// }
// ]
// };
const actions = {
type: 'array',
ref: 'props.actionItems',
label: 'Actions',
itemTitleRef: function (data) {
let v = __.filter(actionOptions, {value: data.actionType});
return (v && v.length > 0) ? v[0].label : data.actionType;
},
allowAdd: true,
allowRemove: true,
addTranslation: 'Add Item',
grouped: true,
items: {
// ActionGroup: actionGroup, // eslint-disable-line capitalized-comments
actionType: {
type: 'string',
ref: 'actionType',
component: 'dropdown',
defaultValue: 'none',
options: actionOptions
},
bookmarkList: {
type: 'string',
ref: 'selectedBookmark',
component: 'dropdown',
label: 'Select bookmark',
expression: 'optional',
options: extHelper.getBookmarkList(),
show: function (data, defs) {
const def = __.find(defs.layout.props.actionItems, {cId: data.cId});
return def && bookmarkEnabler.indexOf(def.actionType) > -1;
}
},
fieldList: {
type: 'string',
ref: 'selectedField',
component: 'dropdown',
label: 'Select field',
defaultValue: '',
options: function () {
return extHelper.getFieldList().then(function (fieldList) {
fieldList.splice(0, 0, {
value: 'by-expr',
label: '>> Define field by expression <<'
});
// Ugly workaround/fix for bug in Qlik Sense 2.1 - 3.1 that will cause
// the loading of the field not to be finished
// $timeout(function () {
// $('.cell').trigger('mouseover');
// }, 0);
return fieldList;
});
},
show: function (data, defs) {
const def = __.find(defs.layout.props.actionItems, {cId: data.cId});
return def && fieldEnabler.indexOf(def.actionType) > -1;
}
},
field: {
type: 'string',
ref: 'field',
label: 'Field',
expression: 'optional',
show: function (data, defs) {
const def = __.find(defs.layout.props.actionItems, {cId: data.cId});
return def && fieldEnabler.indexOf(def.actionType) > -1 && def.selectedField === 'by-expr';
}
},
variable: {
type: 'string',
ref: 'variable',
label: 'Variable name',
expression: 'optional',
show: function (data, defs) {
const def = __.find(defs.layout.props.actionItems, {cId: data.cId});
return def && variableEnabler.indexOf(def.actionType) > -1;
}
},
value: {
type: 'string',
ref: 'value',
label: 'Value',
expression: 'optional',
show: function (data, defs) {
const def = __.find(defs.layout.props.actionItems, {cId: data.cId});
return def && valueEnabler.indexOf(def.actionType) > -1;
}
},
valueDesc: {
type: 'text',
component: 'text',
ref: 'valueDesc',
label: 'Define multiple values separated with a semi-colon (;).',
show: function (data, defs) {
const def = __.find(defs.layout.props.actionItems, {cId: data.cId});
return def && valueDescEnabler.indexOf(def.actionType) > -1;
}
},
overwriteLocked: {
type: 'boolean',
ref: 'softLock',
label: 'Overwrite locked selections',
defaultValue: false,
show: function (data, defs) {
const def = __.find(defs.layout.props.actionItems, {cId: data.cId});
return def && overwriteLockedEnabler.indexOf(def.actionType) > -1;
}
}
}
};
// ****************************************************************************************
// Setup
// ****************************************************************************************
const appearanceSettings = {
uses: 'settings',
items: {
general: {
items: {
showTitles: {
defaultValue: false
}
}
},
layout: {
type: 'items',
label: 'Layout',
items: {
label: buttonLabel,
style: buttonStyle,
buttonStyleExpression: buttonStyleExpression,
buttonStyleCss: buttonStyleCss,
buttonWidth: buttonWidth,
buttonAlignment: buttonAlignment,
buttonTextAlign: buttonTextAlign,
buttonMultiLine: buttonMultiLine,
buttonIcons: buttonIcons
}
},
actionsList: actions,
behavior: {
type: 'items',
label: 'Navigation behavior',
items: {
action: navigationAction,
sheetId: sheetId,
sheetList: sheetList,
storyList: storyList,
websiteUrl: websiteUrl,
sameWindow: sameWindow,
appList: appList
}
}
}
};
// Note for the extension certification process:
// Using the calculation condition is not officially supported!
// But seems to work well and using it is of low risk.
const addons = {
type: 'items',
component: 'expandable-items',
translation: 'properties.addons',
items: {
dataHandling: {
uses: 'dataHandling',
items: {
suppressZero: null
}
}
}
};
// ****************************************************************************************
// Return Values
// ****************************************************************************************
return {
type: 'items',
component: 'accordion',
items: {
settings: appearanceSettings,
addons: addons
},
__test_only__: {
getIcons: getIcons
}
};
});