Last active
August 21, 2018 15:54
-
-
Save theredpea/816627e3c662c43c1baaccd00597ecab to your computer and use it in GitHub Desktop.
selection_project_utility.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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