Skip to content

Instantly share code, notes, and snippets.

@theredpea
Last active August 21, 2018 15:54
Show Gist options
  • Save theredpea/816627e3c662c43c1baaccd00597ecab to your computer and use it in GitHub Desktop.
Save theredpea/816627e3c662c43c1baaccd00597ecab to your computer and use it in GitHub Desktop.
selection_project_utility.js
var BY_QSORTINDEX = function (a, b) {
//6-28-2018 using the *users* selection order
//Otherwise Qlik would make the array in (what kind of order? I can't tell, so I'll call it arbitrary order)
//
return a.qSortIndex - b.qSortIndex;
};
var SIMPLE_QFIELDSELECTION_MAP = function (qFieldSelections) {
// https://help.qlik.com/en-US/sense-developer/April2018/Subsystems/APIs/Content/CapabilityAPIs/SelectionAPI/QSelectionState.htm
//Otherwise JSON.stringify doesn't stringify; is it because of Symbols?
// https://help.qlik.com/en-US/sense-developer/April2018/Subsystems/APIs/Content/CapabilityAPIs/SelectionAPI/QFieldSelections.htm
return {
fieldName: qFieldSelections.fieldName,
qSortIndex: qFieldSelections.qSortIndex,
qSelected: qFieldSelections.qSelected,
selectedValues: qFieldSelections.selectedValues,
totalCount: qFieldSelections.totalCount
}
}
var ISNUM = function (qNxCell) {
return qNxCell.qNum && qNxCell.qNum !== 'NaN';
}
var GET_QNUM_IF_ISNUM_ELSE_QTEXT = function (qNxCell) {
return ISNUM(qNxCell) ? qNxCell.qNum : qNxCell.qText;
}
var DOUBLE_QUOTE_WRAP = function (_) {
return '"' + _ + '"';
}
var SINGLE_QUOTE_WRAP = function (_) {
return ("'" + _ + "'");
}
var GET_VAL_IF_ISNUM_ELSE_SINGLE_QUOTE_WRAP = function (qNxCell) {
//May be overkill; both of these expressions produce the same result for me:
//Though the first uses quotes, the latter doesnt
// Note also that *single quotes* are required
//=COUNT(DISTINCT {$<MixedStringAndNum={'2', 'a'}>} MixedStringAndNum)
//=COUNT(DISTINCT {$<MixedStringAndNum={ 2, 'a'}>} MixedStringAndNum)
//Single quotes:
return ISNUM(qNxCell) ? qNxCell.qNum : SINGLE_QUOTE_WRAP(qNxCell.qText)
}
var CURRENT_SELECTION_TO_SET_CLAUSE = function (equalPart) {
// Didnt just wanna give a function where second param is the equalPart
// because when called with map(...) the second param would be the index;
return function (currentFieldSelection) {
//This object currentFieldSelection is one Nate created;
//its interface is:
// export interface {
// //comes from : https://help.qlik.com/en-US/sense-developer/April2018/Subsystems/APIs/Content/CapabilityAPIs/SelectionAPI/QFieldSelections.htm
// qFieldSelection : qFieldSelections,
//
// // comes from https://help.qlik.com/en-US/sense-developer/3.1/Subsystems/APIs/Content/FieldAPI/QFieldValue.htm
// // {
// // "qText": "1",
// // "qNum": 1,
// // "qElemNumber": 0,
// // "qState": "S"
// // }
// selectedNxCells : qNxCell[]
// }
//Turns it into a part that can be used in a clause of a set expression
//For example, in this set expresion:
// {$<D1={1}, D2={'A'}>}
//There are two clauses:
// D1={1}
// D2={'A'}
//Test with:
// Field whose name contains spaces (square brackets)
// Value which is numeric
// Multiple values, one numeric one not
var fieldPart = '[' + currentFieldSelection.qFieldSelection.fieldName + ']';
var valuePart = '{' + currentFieldSelection.selectedNxCells.map(GET_VAL_IF_ISNUM_ELSE_SINGLE_QUOTE_WRAP).join(',') + '}';
//Using asterisk which is the *intersection* with the SET context ($, or BM01, or $1, or)
//vs *overriding/replacing* what might be in the SET context
var equalPart = equalPart || '*=';
var result = fieldPart + equalPart + valuePart;
return result;
}
}
var CURRENT_SELECTION_TO_SET_EXPRESSION = function (setContextPart, selection) {
var setContextPart = setContextPart || '$';
var clausePart = selection.map(CURRENT_SELECTION_TO_SET_CLAUSE()).join(',');
var result = '{' + setContextPart + '<' + clausePart + '>}';
return result;
}
//TODO: Sort by qSortIndex before returning!
return getCurrentSelections()
.then(function (selectedValuesBeforeBookmark) {
// Using the bookmark the set context
// sum ({BM01} Sales)
// https://help.qlik.com/en-US/sense/April2018/Subsystems/Hub/Content/ChartFunctions/SetAnalysis/set-analysis-expressions.htm
// wrapping in double-quotes in case the bookmark title has spaces in it
var setContextPart = DOUBLE_QUOTE_WRAP(layout_bookmark.qMeta.title);
var setExpression = CURRENT_SELECTION_TO_SET_EXPRESSION(setContextPart, selectedValuesBeforeBookmark);
console.log('Selected values *before Bookmark* , in the order the user applied them, are :');
console.log(JSON.stringify(selectedValuesBeforeBookmark, null, 4));
console.log('Applying bookmark... with qMeta.title: "' + layout_bookmark.qMeta.title + '" and qInfo.qId: "' + layout_bookmark.qInfo.qId + '"');
var countOfMainFieldWithCurrentSelectionsIntersectedWithNewBookmark = 'COUNT(' + setExpression + LIVINGSTON_CLIENT_ACCOUNT_NUMBER_FIELD_NAME + ')';
return qlik.currApp().model.engineApp
// vs qlik.currApp().model.enigmaModel.app.evaluateEx
// which unwraps the qValue
.evaluateEx(countOfMainFieldWithCurrentSelectionsIntersectedWithNewBookmark)
.then(function (countResult) {
var dontRestoreSelectionsBeforeBookmark = false;
if (countResult
&& countResult.qValue
&& countResult.qValue.qIsNumeric
&& countResult.qValue.qNumber === 0) {
dontRestoreSelectionsBeforeBookmark = true;
alert('This layouts\' selections and your current selections are mutually exclusive (0 rows would be returned.) ' +
'We will use the layouts\' selections. To restore your selections, use the selection "back button", or reapply manually one field at a time.');
}
define( [
"js/qlik",
"jquery",
"/selection_project/js/selection_project-environment-config.js",
], function (
qlik,
$,
selection_project_environment_config) {
var newLineDelimeter = '\r\n';
var valuesDelimiter = "|";
var fieldsAndValsSeperator = ",";
var wrapChar = '"';
var _selectionObject = undefined;
var getSelectionObjectLayout = function(){
if (_selectionObject){
getSelectionObjectPromise = qlik.Promise.resolve(_selectionObject);
} else {
getSelectionObjectPromise = qlik.currApp()
.getList("SelectionObject")
.then(function(reply) {
_selectionObject = reply;
return _selectionObject;
});
}
return getSelectionObjectPromise.then(function(selectionObjectModel){
_selectionObject;
return selectionObjectModel.getLayout();
})
}
var _fieldListObjectCache = {};
var getCurrentSelections = function(){
return getSelectionObjectLayout()
.then(function(selectionObjectLayout){
return qlik.Promise.all(selectionObjectLayout
.qSelectionObject
.qSelections
.map(function(qSelectionInfo) {
var FIELD_TO_LIST = qSelectionInfo.qField;
var LIST_LIMIT = 9999;
var fieldListObjectPromise = undefined;
if (_fieldListObjectCache[FIELD_TO_LIST]){
fieldListObjectPromise = qlik.Promise.resolve(_fieldListObjectCache[FIELD_TO_LIST]);
} else {
fieldListObjectPromise = qlik.currApp().model.enigmaModel.createSessionObject({
"qInfo": {
"qType": "listbox"
},
"qListObjectDef": {
"qShowAlternatives": true,
"qDef": {
"qFieldLabels": [
FIELD_TO_LIST
],
"qFieldDefs": [
FIELD_TO_LIST
],
"autoSort": true,
"cId": "",
"qSortCriterias": [
{
"qSortByState": 1,
"qSortByAscii": 1,
"qSortByNumeric": 1,
"qSortByLoadOrder": 1
}
]
}
}
})
.then(function (fieldListObject){
_fieldListObjectCache[FIELD_TO_LIST] = fieldListObject;
return _fieldListObjectCache[FIELD_TO_LIST];
});
}
return fieldListObjectPromise
.then(function (fieldListObject){
return fieldListObject.getListObjectData('/qListObjectDef', [{qTop:0, qLeft: 0 , qHeight: LIST_LIMIT, qWidth: 1}]);
})
.then(function(fieldListObjectData){
return {
qSelectionInfo : qSelectionInfo,
selectedNxCells: fieldListObjectData[0].qMatrix.filter(function(qMatrixRow){
return ['S','L', 'XS'].indexOf(qMatrixRow[0].qState)>-1;
}).map(function(selectedQMatrixRow){
return selectedQMatrixRow[0];
})
};
});
})
);
})
}
var getSelectionCsv = function(selectionArray, saveNumForDuals){
var app = qlik.currApp();
var csvStr = "";
csvStr += '' + wrapChar + 'Field' + wrapChar + fieldsAndValsSeperator + wrapChar + 'Field Values' + wrapChar + newLineDelimeter + '';
selectionArray.forEach(function(currentSelectionObject){
if (currentSelectionObject.qSelectionInfo.qStateCounts.qSelected > 9999) {
alert('Warning: Limited of 9999 selected Field Values per field has been exceeded. A total of ' + getFielddData.stateCounts.qSelected + ' field values values are selected in the field "' + getFielddData.fldname + '". The exported file will contain only 9999 of the ' + getFielddData.stateCounts.qSelected + ' selected values.');
}
var fieldName = currentSelectionObject.qSelectionInfo.qField;
csvStr += wrapChar + fieldName + wrapChar + fieldsAndValsSeperator + '';
csvStr += '' + wrapChar + '';
var items = '';
var stringifiedSelections = currentSelectionObject
.selectedNxCells
.filter(function(selectedOrLockedValue){
return selectedOrLockedValue.qText;
});
stringifiedSelections.forEach(function(selectedOrLockedValue, valueIndex){
var stringifyValue = selectedOrLockedValue.qText;
if (saveNumForDuals){
switch (fieldName.toLowerCase()) {
case 'field_which_is_a_dual':
stringifyValue = selectedOrLockedValue.qNum;
break;
}
}
items += (valueIndex > 0 ? valuesDelimiter : '') + stringifyValue + '';
});
if (stringifiedSelections.length > 1) {
items = '(' + items + ')';
}
csvStr += items + '' + wrapChar + '';
csvStr += newLineDelimeter;
});
return csvStr;
}
var getCurrentSelectionsCsv = function(saveNumForDuals){
return getCurrentSelections()
.then(function(currentSelectionArray){
return getSelectionCsv(currentSelectionArray, saveNumForDuals);
})
}
var downloadStringAsFile = function(csvStr) {
var stamp = new Date().toString().split(" ").splice(0, 5).join("-").split(":").join("-")
var fileType = "text/csv;charset=UTF-8";
var extension = "csv";
var fileName = "Selections-" + stamp + "." + extension;
var blob = new Blob([csvStr], {
type: fileType
});
if (blob && typeof(window.navigator.msSaveOrOpenBlob) === 'function') {
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else if (blob) {
var url = window.URL.createObjectURL(blob);
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
} else {
console.error('There is no blob');
}
};
var getParsedSelections= function(selectionObj){
var parsedSelections = [];
var trueFieldsAndValsSeparator = /","/g;
var unambiguousFieldsAndValsSeparator = '|||';
var stringSelection = selectionObj.data
.replace(trueFieldsAndValsSeparator, unambiguousFieldsAndValsSeparator)
.replace(/"/g, '');
var lines = stringSelection.split('\r\n');
lines.splice(0,1);
for (var i=0; i< lines.length; i++) {
var line = lines[i], values;
var splitRow = line.split(unambiguousFieldsAndValsSeparator);
var fieldName = splitRow[0];
var fieldValue = splitRow[1] || '';
if (fieldName && fieldName.toLowerCase() != 'field_which_should_be_ignored') {
var parensMatch = fieldValue.match(/^\((.*)\)$/);
if (parensMatch) {
fieldValue = parensMatch[1];
}
values = fieldValue.split('|') || [];
var method;
var valueToSelect;
if (dualFields.indexOf(fieldName.toLowerCase())>-1) {
console.log(fieldName + ' is one of the dualFields, therefore parsing all of its values to Number (i.e. qNum) before applying; and using method: selectValues');
method = 'selectValues';
valueToSelect = values.map(Number) || [];
if (valueToSelect.some(isNaN)){
console.error('Trying to parse values to numbers but some failed (i.e. NaN) ');
console.error('String values are: ', values);
console.error('Attempted numbers are: ', valueToSelect);
}
} else {
console.log(fieldName + ' is not included in dualField, therefore usings its string (i.e. qText) values, and using method: selectMatch');
method = 'selectMatch';
valueToSelect = '('+values.map(function(val){ return '"' + val + '"';}).join('|') +')';
}
parsedSelections.push({field : fieldName, value : valueToSelect , method : method});
}
}
return parsedSelections;
};
var applyParsedSelections = function(parsedSelections){
var app = qlik.currApp();
console.log('%c---APPLYING SELECTIONS ', 'color:green;');
console.log('%c' + JSON.stringify(parsedSelections, true, 4), 'color:green;');
return qlik.Promise.all(parsedSelections.map(function(fieldSelectorObj){
var waitMiliseconds = 1000;
return qlik.Promise.race([
app.field(fieldSelectorObj.field).waitFor,
qlik.Promise(function(resolve, reject){
setTimeout(function(){
reject('Could not find field "' + fieldSelectorObj.field+'" after '+waitMiliseconds+' miliseconds, is the field name spelled correctly? Case sensitive.');
}, waitMiliseconds);
})
]).catch(function(err){
console.error(err);
return true;
});
})
)
.then(function() {
console.log('done waiting for fields ; now calling clearAll');
var clearLockedFields = false;
app.clearAll(clearLockedFields);
return qlik.Promise.all(parsedSelections.map(function(fieldSelectorObj) {
return app.field(fieldSelectorObj.field)[fieldSelectorObj.method](fieldSelectorObj.value).then(function(selectResult) {
console.log((selectResult ? '' : '!') + fieldSelectorObj.method + " finished; field is : "+ fieldSelectorObj.field + " method is: " + fieldSelectorObj.method + " result is: " + selectResult + " and value is : " , fieldSelectorObj.value);
return selectResult;
});
})
);
})
.catch(function(getFieldOrSelectError){
console.error('Something went wrong either when GetField or SelectValues');
console.error(getFieldOrSelectError);
});
};
var getSimpleSelectionObject = function(qFieldSelection){
return {
fieldName: qFieldSelection.fieldName,
locked : qFieldSelection.locked,
selectedCount: qFieldSelection.selectedCount,
totalCount: qFieldSelection.totalCount,
qSelected : qFieldSelection.qSelected
};
};
return {
getCurrentSelections : getCurrentSelections,
getSelectionCsv : getSelectionCsv,
getCurrentSelectionsCsv : getCurrentSelectionsCsv,
applyParsedSelections : applyParsedSelections,
getParsedSelections : getParsedSelections,
getSimpleSelectionObject: getSimpleSelectionObject,
downloadStringAsFile : downloadStringAsFile
};
} );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment