UNPKG

sparnatural

Version:

Visual client-side SPARQL query builder and knowledge graph exploration tool

343 lines (284 loc) 12.4 kB
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; }