sparnatural
Version:
Visual client-side SPARQL query builder and knowledge graph exploration tool
343 lines (284 loc) • 12.4 kB
text/typescript
import UiuxConfig from "../../IconsConstants";
import { SelectedVal } from "../../../components/SelectedVal";
import ISparnaturalSpecification from "../../../spec-providers/ISparnaturalSpecification";
import HTMLComponent from "../../HtmlComponent";
import VariableOrderMenu from "./VariableOrderMenu";
import { I18n } from "../../../settings/I18n";
import { AggregateFunction, VariableExpression, VariableTerm } from "../../../generators/json/ISparJson";
/*
Single Draggable Component
It consists of an "drag handle" + icon + name of the variable
The name of the variable can be edited.
*/
export class DraggableComponent extends HTMLComponent {
aggrComponentAction: JQuery<HTMLElement>;
aggrComponentOptions: JQuery<HTMLElement>;
aggrComponentInput: JQuery<HTMLElement>;
aggrComponentBadgeValue: JQuery<HTMLElement>;
aggrComponentOptionsExtend: JQuery<HTMLElement>;
parentComponent: VariableOrderMenu;
state: DraggableComponentState;
// listener
varEdited: (state: DraggableComponentState, previousVarName: SelectedVal) => void;
aggrChanged: (state: DraggableComponentState) => void;
constructor(
parentComponent: VariableOrderMenu,
specProvider: ISparnaturalSpecification,
selected_val: SelectedVal,
varEdited: (state: DraggableComponentState, previousVarName:SelectedVal) => void,
aggrChanged: (state: DraggableComponentState) => void
) {
let varName = selected_val.variable;
let icon = specProvider.getEntity(selected_val.type).getIcon();
let icon_display = `` ;
if (icon != undefined ) {
icon_display = `<div class="tmpicon"><span><i class="fa ${icon} fa-fw"></i></span></div>` ;
}
let editVar = $(`
<input type="text" minlength="1">
</input>
`).val(varName);
let aggrActionIput = $(`<input type="hidden" name="selectedAggr" />`) ;
let aggrAction = $(`
<div class="variableSelectedAggr flexWrap" data-variableName="${varName}">
<span class="variableAggr-handle">
${UiuxConfig.ICON_ARROW_BOTTOM}
</span>
</div>`) ;
let aggrOptions = $(`
<div class="aggrOptions reducted is-num is-time" style="display: none;">
<ul>
<li data-value="" class="reducted-visible">`+I18n.labels.AggrLabelNone+`</li>
<li data-value="count" data class="reducted-visible" data-suffix="_count">`+I18n.labels.AggrLabelCount+`</li>
<li data-value="group_concat" data-suffix="_group_concat">`+I18n.labels.AggrLabelGroupConcat+`</li>
<li data-value="max" data-suffix="_max" class="revealIf revealIf-num revealIf-time">`+I18n.labels.AggrLabelMax+`</li>
<li data-value="min" data-suffix="_min" class="revealIf revealIf-num revealIf-time">`+I18n.labels.AggrLabelMin+`</li>
<li data-value="sample" data-suffix="_sample">`+I18n.labels.AggrLabelSample+`</li>
<li data-value="sum" data-suffix="_sum" class="revealIf revealIf-num">`+I18n.labels.AggrLabelSum+`</li>
<li data-value="avg" data-suffix="_avg" class="revealIf revealIf-num">`+I18n.labels.AggrLabelAvg+`</li>
</ul>
</div>`) ;
let aggrOptionsExtend = $(`
<div class="aggrOptionsExtend">
<span class="reducted-action">`+I18n.labels.AggrLabelSeeMore+`</span>
<span class="extended-action">`+I18n.labels.AggrLabelSeeLess+`</span>
</div>`) ;
let widgetHtml =
$(`<div class="variableSelected flexWrap" data-variableName="${varName}">
<span class="variable-handle">
${UiuxConfig.COMPONENT_DRAG_HANDLE}
</span>
${icon_display}
</div>`).append(editVar) ;
let aggrBadgeValue = $(`<div class="aggrBadgeValue" style="display: none;"></div>
</div>`) ;
$(aggrAction).append(aggrActionIput);
$(widgetHtml).append(aggrAction);
$(aggrOptions).append(aggrOptionsExtend);
$(widgetHtml).append(aggrOptions);
$(widgetHtml).append(aggrBadgeValue);
super("sortableItem", parentComponent, widgetHtml);
this.state = {
// make a COPY of the Selected val
// otherwise any change to that object is also reflected in the Start/EndClassGroup
selectedVariable: { ...selected_val }
} ;
this.#resize(editVar, varName);
this.varEdited = varEdited;
this.aggrChanged = aggrChanged;
this.aggrComponentAction = aggrAction ;
this.aggrComponentOptionsExtend = aggrOptionsExtend ;
this.aggrComponentOptions = aggrOptions ;
this.aggrComponentInput = aggrActionIput ;
this.aggrComponentBadgeValue = aggrBadgeValue ;
let that = this;
editVar[0].addEventListener("change", (event) => {
//variableName got edited by user
let val = this.#validateInput(event.currentTarget as HTMLInputElement);
that.onVarNameChange(val);
});
this.initEventListenersAggr() ;
if (parentComponent.aggrOptionsExtend) {
this.toggleAggrOptionsExtend() ;
}
}
/**
* This is called from QueryLoader when loading query
*/
loadAggregatedVariable(aggregatedVar:VariableExpression) {
// need to init aggregate funtion selection
// set this/state for agregate function context
this.state.aggregateFunction = this.getAggregateFunctionByValue(aggregatedVar.expression.aggregation) ;
this.state.originalVariable = { ...this.state.selectedVariable }
this.state.selectedVariable.variable = aggregatedVar.variable.value ;
//Init UI/UX elements
this.onAggrOptionSelected(this.state.aggregateFunction as string) ;
this.widgetHtml.find("input").val(this.state.selectedVariable.variable);
this.#resize(this.widgetHtml.find("input"), this.state.selectedVariable.variable);
}
render(): this {
this.htmlParent = $(this.parentComponent.html).find(
".variablesOtherSelect"
);
super.render();
return this;
}
initEventListenersAggr() {
//Toggle option menu display
this.aggrComponentAction[0].addEventListener("click", (event: Event) => {
this.toggleAggrOption() ;
});
// Capture aggregate function selection
this.aggrComponentOptions[0].querySelectorAll('li').forEach((optionItem) => {
optionItem.addEventListener("click", (event: Event) => {
let option = event.currentTarget as HTMLElement ;
let optionValue = option.getAttribute('data-value');
let optionValueSuffix = option.getAttribute('data-suffix');
if(!this.state.aggregateFunction) {
// no aggr function was selected, store original variable
// no NOT simply assign the selected variable to the original variable
// but create a new object
this.state.originalVariable = {variable: this.state.selectedVariable.variable, type: this.state.selectedVariable.type} ;
}
if(optionValue == '') {
// no aggr function selected : reset to original var name
this.state.aggregateFunction = undefined ;
this.state.selectedVariable = this.state.originalVariable ;
} else {
this.state.aggregateFunction = this.getAggregateFunctionByValue(optionValue) ;
}
if(this.state.aggregateFunction) {
// compute new variable name
this.state.selectedVariable.variable = this.state.originalVariable.variable+optionValueSuffix ;
}
this.onAggrOptionSelected(optionValue) ;
// notify event
this.aggrChanged(this.state);
// set the input value to the new variable name
this.widgetHtml.find("input").val(this.state.selectedVariable.variable);
this.#resize(this.widgetHtml.find("input"), this.state.selectedVariable.variable);
});
});
this.aggrComponentOptionsExtend[0].addEventListener("click", (event: Event) => {
this.toggleAggrOptionsExtend() ;
});
}
onVarNameChange(newName:string) {
// recreate a new object, otherwise this will get changed too !
let previousVarName = { ... this.state.selectedVariable };
this.state.selectedVariable.variable = newName;
let editVar = this.widgetHtml.find("input");
this.#resize(editVar, newName);
// if there is currently an aggregate function, display badge
if(this.state.aggregateFunction) {
this.displayBadgeValue(this.aggrComponentOptions[0].querySelector('li.selected').innerHTML ) ;
}
// call callback
this.varEdited(this.state, previousVarName);
}
onAggrOptionSelected(option:string) {
let optionItemLabel = '' ;
this.aggrComponentOptions[0].querySelectorAll('li').forEach((optionItem) => {
if(optionItem.getAttribute('data-value') == option) {
optionItemLabel = optionItem.innerHTML ;
optionItem.classList.add('selected');
} else {
optionItem.classList.remove('selected');
}
}) ;
(<HTMLInputElement>this.aggrComponentInput[0]).value = option ;
this.closeAggrOptions() ;
this.displayBadgeValue(optionItemLabel) ;
}
toggleAggrOption() {
if(this.aggrComponentOptions[0].style.display == 'block') {
return this.closeAggrOptions() ;
}
if(this.parentComponent.aggrOptionsExtend) {
this.aggrComponentOptions[0].classList.remove('reducted');
this.aggrComponentOptions[0].classList.add('extended');
} else {
this.aggrComponentOptions[0].classList.add('reducted');
this.aggrComponentOptions[0].classList.remove('extended');
}
return this.onpenAggrOptions() ;
}
toggleAggrOptionsExtend() {
if(this.aggrComponentOptions[0].classList.contains('reducted')) {
this.aggrComponentOptions[0].classList.remove('reducted');
this.aggrComponentOptions[0].classList.add('extended');
this.parentComponent.aggrOptionsExtend = true;
} else {
this.aggrComponentOptions[0].classList.add('reducted');
this.aggrComponentOptions[0].classList.remove('extended');
this.parentComponent.aggrOptionsExtend = false;
}
}
onpenAggrOptions() {
this.aggrComponentOptions[0].style.display = 'block';
}
closeAggrOptions() {
this.aggrComponentOptions[0].style.display = 'none';
}
/**
* Displays the aggregation function badge from the selected option
*/
displayBadgeValue(option: string) {
// if there is an aggregation function in the state...
if (this.state.aggregateFunction) {
this.aggrComponentBadgeValue[0].style.display = 'block';
this.aggrComponentBadgeValue[0].innerText = option+'('+this.state.originalVariable.variable+')' ;
} else {
this.aggrComponentBadgeValue[0].style.display = 'none';
}
}
setVarName(newName:string) {
this.onVarNameChange(newName);
this.widgetHtml.find("input").val(newName);
}
#resize(el: JQuery<HTMLElement>, varName: string): void {
el[0].style.width = 10 + "ch";
if (varName.length > 10) el[0].style.width = varName.length + "ch";
}
#validateInput(inputEl: HTMLInputElement) {
if (inputEl.value.length < 1) {
let keepValue = this.state.selectedVariable.variable;
inputEl.value = keepValue; // keep old variable
return keepValue;
}
// if there is an aggregation function
if (this.state.aggregateFunction) {
// prevent the varName from being equal to the original varName
if(inputEl.value == this.state.originalVariable.variable) {
let keepValue = this.state.selectedVariable.variable;
inputEl.value = keepValue
return keepValue;
}
}
return inputEl.value;
}
/**
* @returns the enum value from the string value
*/
getAggregateFunctionByValue(value: string) {
let str: string = value;
let aggregateFunction:AggregateFunction = str as AggregateFunction;
return aggregateFunction ;
}
}
export interface DraggableComponentState {
/**
* Name of the variable in the white input field in the middle
* (can be the original var name or the var name of the result of the aggreation)
* Also contains the type (= class)
*/
selectedVariable:SelectedVal;
/**
* Name of the aggregation function
*/
aggregateFunction?:AggregateFunction;
/**
* In case an aggregation function is selected, the name of the original var name
*/
originalVariable?:SelectedVal;
}