sparnatural
Version:
Visual client-side SPARQL query builder and knowledge graph exploration tool
189 lines (153 loc) • 6.14 kB
text/typescript
import { SelectedVal } from "../SelectedVal";
import { AbstractWidget, RDFTerm, RdfTermValue, ValueRepetition, WidgetValue } from "./AbstractWidget";
import { DataFactory } from 'rdf-data-factory';
import "select2";
import "select2/dist/css/select2.css";
import { ListDataProviderIfc, RdfTermDatasourceItem, NoOpListDataProvider, mergeDatasourceResults } from "./data/DataProviders";
import { I18n } from "../../settings/I18n";
import { Term } from "@rdfjs/types/data-model";
import HTMLComponent from "../HtmlComponent";
const factory = new DataFactory();
export interface ListConfiguration {
dataProvider: ListDataProviderIfc,
values?: Term[]
}
export class ListWidget extends AbstractWidget {
// The default implementation of ListConfiguration
static defaultConfiguration: ListConfiguration = {
dataProvider: new NoOpListDataProvider(),
values: undefined
}
configuration: ListConfiguration;
selectHtml: JQuery<HTMLElement>;
constructor(
parentComponent: HTMLComponent,
config: ListConfiguration,
startClassVal: SelectedVal,
objectPropVal: SelectedVal,
endClassVal: SelectedVal
) {
super(
"list-widget",
parentComponent,
null,
startClassVal,
objectPropVal,
endClassVal,
ValueRepetition.MULTIPLE
);
this.configuration = config;
this.startClassVal = startClassVal;
this.objectPropVal = objectPropVal;
this.endClassVal = endClassVal;
}
render() {
super.render();
this.selectHtml = $(`<select style="width:100%; min-width:200px;"></select>`);
this.html.append(this.selectHtml);
let noItemsHtml =
$(`<div class="no-items" style="display: none; font-style:italic;">
${I18n.labels.ListWidgetNoItem}
</div>`);
let errorHtml =
$(`<div class="no-items" style="display: none; font-style:italic;">
${I18n.labels.ListWidgetNoItem}
</div>`);
let callback = (items:RdfTermDatasourceItem[]) => {
if (items.length > 0) {
this.selectHtml.append(
$("<option value=''>" + I18n.labels.ListWidgetSelectValue + "</option>")
);
// find distinct values of the 'group' binding
const groups = [...new Set(items.map(item => item.group))];
if(groups.length == 1 && groups[0] == undefined) {
// no groups were defined at all
items.forEach(item => {
// select item label : either displayed label, or itemLabel if provided
let itemLabel = item.itemLabel?item.itemLabel:item.label;
this.selectHtml.append(
$("<option value='" + JSON.stringify(item.term) + "' data-itemLabel='"+itemLabel+"'>" + item.label + "</option>")
);
});
} else {
// we found some groups, organise the list content with optgroup
let mergedResult = mergeDatasourceResults(items);
const groupsAfterMerge = [...new Set(mergedResult.map(item => item.group))];
groupsAfterMerge.forEach(group => {
let html = "<optgroup label=\""+group+"\">";
mergedResult.filter(item => (item.group == group)).forEach(item => {
// select item label : either displayed label, or itemLabel if provided
let itemLabel = item.itemLabel?item.itemLabel:item.label;
html += "<option value='" + JSON.stringify(item.term) + "' data-itemLabel='"+itemLabel+"'>" + item.label + "</option>";
});
html += "</optgroup>"
this.selectHtml.append($(html));
})
}
this.selectHtml = this.selectHtml.select2({
// use the minimumResultsForSearch parameter to avoid using a search box when only a few items are present
minimumResultsForSearch: 20,
// pass a JQUery object so that HTML markup is preserved
// TODO : this does not work ATM
// templateResult: function formatLabel(label:any) {return $(label)},
width: "style"
});
// set a listener for when a value is selected
this.selectHtml.on("select2:close", (e: any) => {
let option = (e.currentTarget as HTMLSelectElement).selectedOptions;
if (option.length > 1)
throw Error("List widget should allow only for one el to be selected!");
// this is the placeholder
if(option[0].value == "")
return;
let itemLabel = option[0].getAttribute("data-itemLabel");
let listWidgetValue: WidgetValue = this.buildValue(option[0].value, itemLabel);
this.triggerRenderWidgetVal(listWidgetValue);
});
} else {
this.html.append(noItemsHtml);
}
// switch off spinner
this.toggleSpinner('')
}
// TODO : this is not working for now
let errorCallback = (payload:any) => {
console.log(payload);
this.html.append(errorHtml);
}
// toggle spinner before loading
this.toggleSpinner(I18n.labels.AutocompleteSpinner_Searching);
// if there are some provided values like in sh:in...
if(this.configuration.values?.length > 0) {
// convert the provided list of terms to RDFTerm[]
let items: {term:RDFTerm;label:string;group?:string}[] = [];
this.configuration.values.forEach(v => {
items.push({
term: new RDFTerm(v),
label:v.value
});
});
// then call the callback with it
callback(items);
} else {
this.configuration.dataProvider.getListContent(
this.startClassVal.type,
this.objectPropVal.type,
this.endClassVal.type,
callback,
errorCallback
);
}
return this;
}
// separate the creation of the value from the widget code itself
// so that it can be overriden by LiteralListWidget
buildValue(termString:string,label:string): WidgetValue {
let term = (JSON.parse(termString) as RDFTerm);
return new RdfTermValue({
label: label,
rdfTerm: term
});
}
parseInput(input:RdfTermValue["value"]): WidgetValue { return new RdfTermValue(input) }
}