ui-framework-jps
Version:
A simple UI framework for state management and UI components
547 lines • 31.2 kB
JavaScript
import { CollectionViewSorterDirection, EXTRA_ACTION_ATTRIBUTE_NAME, Modifier } from "../../ConfigurationTypes";
import browserUtil from "../../../util/BrowserUtil";
import debug from "debug";
import { ContextualInformationHelper } from "../../context/ContextualInformationHelper";
import { FieldType } from "../../../model/DataObjectTypeDefs";
import { BasicFieldOperations } from "../../../model/BasicFieldOperations";
import { DATA_ID_ATTRIBUTE } from "../../../CommonTypes";
const logger = debug('tabular-view-renderer-with-context');
const paginationLogger = debug('tabular-view-renderer-with-context:pagination');
const loggerEvent = debug('tabular-view-renderer-with-context-event');
export class TabularViewRendererUsingContext {
constructor(view, eventHandler, tableConfig) {
this.tableBodyEl = null;
this.stateBuffer = [];
this.currentPage = 0;
this.observer = null;
this.currentObservedTarget = null;
this.view = view;
this.eventHandler = eventHandler;
this.tableConfig = tableConfig;
this.renderNextPage = this.renderNextPage.bind(this);
}
insertDisplayElementForCollectionItem(containerEl, collectionName, item) {
if (this.tableBodyEl)
ContextualInformationHelper.getInstance().insertDisplayElementForCollectionItem(this.view, this, this.tableBodyEl, collectionName, item, true);
}
removeDisplayElementForCollectionItem(containerEl, collectionName, item) {
if (this.tableBodyEl)
ContextualInformationHelper.getInstance().removeDisplayElementForCollectionItem(this.view, this, this.tableBodyEl, collectionName, item);
}
updateDisplayElementForCollectionItem(containerEl, collectionName, item) {
if (this.tableBodyEl)
ContextualInformationHelper.getInstance().updateDisplayElementForCollectionItem(this.view, this, this.tableBodyEl, collectionName, item);
}
createDisplayElementForCollectionItem(collectionName, item) {
const canDeleteItem = this.view.hasPermissionToDeleteItemInNamedCollection(collectionName, item);
const uiConfig = this.view.getCollectionUIConfig();
logger(`view ${this.view.getName()}: creating table row item`);
logger(item);
const resultDataKeyId = this.view.getIdForItemInNamedCollection(collectionName, item);
let tableRowEl = document.createElement(uiConfig.resultsElement.type);
browserUtil.addClasses(tableRowEl, uiConfig.resultsElement.classes);
browserUtil.addAttributes(tableRowEl, uiConfig.resultsElement.attributes);
let editableFields = [];
if (this.tableConfig.editableFields) {
editableFields = this.tableConfig.editableFields;
}
// we need to build the row from the displayed item values using the renderer if present
this.tableConfig.columns.forEach((column, index) => {
// is the column editable?
const foundIndex = editableFields.findIndex((editableField) => editableField === column.field.id);
const isEditable = (foundIndex >= 0);
const fieldValue = column.getValue(column, item[column.field.id]);
const fieldDataType = column.field.type;
let tdEl = document.createElement('td');
browserUtil.addClasses(tdEl, column.elementClasses);
browserUtil.addAttributes(tdEl, column.elementAttributes);
if ((index + 1) === this.tableConfig.itemDetailColumn) {
// this column is different and can have many components
// the content may be structured
if (uiConfig.detail.containerClasses) {
let contentEl = document.createElement('div');
browserUtil.addClasses(contentEl, uiConfig.detail.containerClasses);
let textEl = document.createElement(uiConfig.detail.textElement.type);
browserUtil.addClasses(textEl, uiConfig.detail.textElement.classes);
browserUtil.addAttributes(textEl, uiConfig.detail.textElement.attributes);
// add the key ids for selection
this.view.renderDisplayForItemInNamedCollection(textEl, collectionName, item);
contentEl.appendChild(textEl);
if (uiConfig.detail.background) {
let imgEl = document.createElement(uiConfig.detail.background.type);
browserUtil.addClasses(imgEl, uiConfig.detail.background.classes);
imgEl.setAttribute('src', this.view.getBackgroundImageForItemInNamedCollection(collectionName, item));
contentEl.appendChild(imgEl);
}
if (uiConfig.detail.badge) {
const badgeValue = this.view.getBadgeValueForItemInNamedCollection(collectionName, item);
if (badgeValue > 0) {
let badgeEl = document.createElement(uiConfig.detail.badge.type);
browserUtil.addClasses(badgeEl, uiConfig.detail.badge.classes);
browserUtil.addAttributes(badgeEl, uiConfig.detail.badge.attributes);
contentEl.appendChild(badgeEl);
badgeEl.innerHTML = ` ${badgeValue} `;
}
}
// add icons
const icons = this.view.getItemIcons(collectionName, item);
icons.forEach((icon) => {
let iconEl = document.createElement('i');
browserUtil.addClasses(iconEl, icon);
if (this.view.prependItemIcons(collectionName, item)) {
contentEl.prepend(iconEl);
}
else {
contentEl.append(iconEl);
}
});
tdEl.appendChild(contentEl);
}
}
else {
switch (fieldDataType) {
case FieldType.colour: {
browserUtil.addAttributes(tdEl, [{ name: "style", value: `background-color:${fieldValue}` }]);
tdEl.innerHTML = ' ';
break;
}
case FieldType.boolean: {
let checkboxEl = document.createElement('input');
browserUtil.addAttributes(checkboxEl, [{ name: 'type', value: 'checkbox' }]);
logger(`Field type is boolean, with value ${fieldValue}`);
if (fieldValue) {
checkboxEl.checked = true;
}
tdEl.appendChild(checkboxEl);
if (isEditable) {
checkboxEl.addEventListener('change', (event) => {
loggerEvent(`Checkbox with field ${column.field.id} changed to value ${checkboxEl.checked}`);
const fieldId = column.field.id;
const context = ContextualInformationHelper.getInstance().findContextFromEvent(event);
if (context) {
this.view.setFieldValue(context.identifier, fieldId, checkboxEl.checked);
}
});
checkboxEl.addEventListener('click', (event) => {
loggerEvent(`Checkbox with field ${column.field.id} clicked to value ${checkboxEl.checked}`);
event.stopPropagation();
//event.preventDefault();
const fieldId = column.field.id;
const context = ContextualInformationHelper.getInstance().findContextFromEvent(event);
if (context) {
this.view.setFieldValue(context.identifier, fieldId, checkboxEl.checked);
}
});
}
else {
checkboxEl.disabled = true;
}
break;
}
case FieldType.text: {
if (isEditable) {
let textEl = document.createElement('input');
browserUtil.addAttributes(textEl, [{ name: 'type', value: 'text' }]);
if (fieldValue) {
textEl.value = fieldValue;
}
tdEl.appendChild(textEl);
textEl.addEventListener('blur', (event) => {
loggerEvent(`field ${column.field.id} changed to value ${textEl.value}`);
const fieldId = column.field.id;
const context = ContextualInformationHelper.getInstance().findContextFromEvent(event);
if (context) {
this.view.setFieldValue(context.identifier, fieldId, textEl.value);
}
});
break;
}
}
case FieldType.integer:
case FieldType.float: {
if (isEditable) {
let textEl = document.createElement('input');
browserUtil.addAttributes(textEl, [{ name: 'type', value: 'number' }]);
if (fieldValue) {
textEl.value = fieldValue;
}
tdEl.appendChild(textEl);
textEl.addEventListener('blur', (event) => {
loggerEvent(`field ${column.field.id} changed to value ${textEl.value}`);
const fieldId = column.field.id;
const context = ContextualInformationHelper.getInstance().findContextFromEvent(event);
if (context) {
if (BasicFieldOperations.getInstance().isValidValue(column.field, textEl.value)) {
browserUtil.addClasses(textEl, 'is-valid');
browserUtil.removeClasses(textEl, 'is-invalid');
this.view.setFieldValue(context.identifier, fieldId, textEl.value);
}
else {
browserUtil.addClasses(textEl, 'is-invalid');
browserUtil.removeClasses(textEl, 'is-valid');
}
}
});
break;
}
}
case FieldType.time:
case FieldType.shortTime:
case FieldType.date:
case FieldType.datetime:
case FieldType.duration: {
if (isEditable) {
let textEl = document.createElement('input');
browserUtil.addAttributes(textEl, [{ name: 'type', value: 'text' }]);
if (fieldValue) {
textEl.value = fieldValue;
}
tdEl.appendChild(textEl);
textEl.addEventListener('blur', (event) => {
loggerEvent(`field ${column.field.id} changed to value ${textEl.value}`);
const fieldId = column.field.id;
const context = ContextualInformationHelper.getInstance().findContextFromEvent(event);
if (context) {
// validate the value
if (BasicFieldOperations.getInstance().isValidValue(column.field, textEl.value)) {
browserUtil.addClasses(textEl, 'is-valid');
browserUtil.removeClasses(textEl, 'is-invalid');
this.view.setFieldValue(context.identifier, fieldId, textEl.value);
}
else {
browserUtil.addClasses(textEl, 'is-invalid');
browserUtil.removeClasses(textEl, 'is-valid');
}
}
});
break;
}
}
default: {
if (fieldValue) {
tdEl.innerText = fieldValue;
}
else {
tdEl.innerText = '';
}
}
}
}
tableRowEl.appendChild(tdEl);
});
// we add an extra column for any actions or the delete function
if (this.tableConfig.actionColumn) {
// create the extra table column
let tdEl = document.createElement('td');
browserUtil.addClasses(tdEl, this.tableConfig.actionColumn.element.classes);
browserUtil.addAttributes(tdEl, this.tableConfig.actionColumn.element.attributes);
if (uiConfig.extraActions) {
uiConfig.extraActions.forEach((extraAction) => {
const hasPermissionForAction = this.view.hasPermissionForActionOnItemInNamedCollection(extraAction.name, collectionName, item);
if (hasPermissionForAction) {
let action = document.createElement('button');
action.setAttribute('type', 'button');
browserUtil.addClasses(action, extraAction.button.classes);
browserUtil.addAttributes(action, extraAction.button.attributes);
if (extraAction.button.text) {
action.innerHTML = extraAction.button.text;
}
if (extraAction.button.iconClasses) {
let iconEl = document.createElement('i');
browserUtil.addClasses(iconEl, extraAction.button.iconClasses);
iconEl.setAttribute(EXTRA_ACTION_ATTRIBUTE_NAME, extraAction.name);
action.appendChild(iconEl);
}
action.setAttribute(EXTRA_ACTION_ATTRIBUTE_NAME, extraAction.name);
action.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
this.eventHandler.eventActionClicked(event);
});
tdEl.appendChild(action);
}
});
}
if (uiConfig.detail.delete && canDeleteItem) {
let deleteButtonEl = document.createElement('button');
deleteButtonEl.setAttribute('type', 'button');
browserUtil.addClasses(deleteButtonEl, uiConfig.detail.delete.classes);
browserUtil.addAttributes(deleteButtonEl, uiConfig.detail.delete.attributes);
if (uiConfig.detail.delete.text) {
deleteButtonEl.innerHTML = uiConfig.detail.delete.text;
}
if (uiConfig.detail.delete.iconClasses) {
let iconEl = document.createElement('i');
browserUtil.addClasses(iconEl, uiConfig.detail.delete.iconClasses);
deleteButtonEl.appendChild(iconEl);
}
deleteButtonEl.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
this.eventHandler.eventDeleteClickItem(event);
});
tdEl.appendChild(deleteButtonEl);
}
tableRowEl.appendChild(tdEl);
}
if (uiConfig.detail.drag) {
tableRowEl.setAttribute('draggable', 'true');
tableRowEl.addEventListener('dragstart', this.eventHandler.eventStartDrag);
}
// add selection actions
if (uiConfig.detail.select) {
tableRowEl.addEventListener('click', this.eventHandler.eventClickItem);
}
// add modifiers for patient state
if (uiConfig.modifiers) {
const modifier = this.view.getModifierForItemInNamedCollection(collectionName, item);
const secondModifier = this.view.getSecondaryModifierForItemInNamedCollection(collectionName, item);
switch (modifier) {
case Modifier.normal: {
logger(`view ${this.view.getName()}: normal item`);
browserUtil.addClasses(tableRowEl, uiConfig.modifiers.normal);
if (uiConfig.icons && uiConfig.icons.normal) {
let iconEl = document.createElement('i');
browserUtil.addClasses(iconEl, uiConfig.icons.normal);
//textEl.appendChild(iconEl);
}
switch (secondModifier) {
case Modifier.warning: {
browserUtil.removeClasses(tableRowEl, uiConfig.modifiers.normal);
browserUtil.addClasses(tableRowEl, uiConfig.modifiers.warning);
if (uiConfig.icons && uiConfig.icons.warning) {
let iconEl = document.createElement('i');
browserUtil.addClasses(iconEl, uiConfig.icons.warning);
//textEl.appendChild(iconEl);
}
break;
}
case Modifier.active: {
if (uiConfig.icons && uiConfig.icons.active) {
let iconEl = document.createElement('i');
browserUtil.addClasses(iconEl, uiConfig.icons.active);
//textEl.appendChild(iconEl);
}
}
}
break;
}
case Modifier.active: {
logger(`view ${this.view.getName()}: active item`);
browserUtil.removeClasses(tableRowEl, uiConfig.modifiers.normal);
browserUtil.addClasses(tableRowEl, uiConfig.modifiers.active);
if (uiConfig.icons && uiConfig.icons.active) {
let iconEl = document.createElement('i');
browserUtil.addClasses(iconEl, uiConfig.icons.active);
//textEl.appendChild(iconEl);
}
switch (secondModifier) {
case Modifier.warning: {
browserUtil.removeClasses(tableRowEl, uiConfig.modifiers.active);
browserUtil.addClasses(tableRowEl, uiConfig.modifiers.warning);
if (uiConfig.icons && uiConfig.icons.warning) {
let iconEl = document.createElement('i');
browserUtil.addClasses(iconEl, uiConfig.icons.warning);
//textEl.appendChild(iconEl);
}
break;
}
}
break;
}
case Modifier.inactive: {
logger(`view ${this.view.getName()}: inactive item`);
browserUtil.removeClasses(tableRowEl, uiConfig.modifiers.normal);
browserUtil.addClasses(tableRowEl, uiConfig.modifiers.inactive);
if (uiConfig.icons && uiConfig.icons.inactive) {
let iconEl = document.createElement('i');
browserUtil.addClasses(iconEl, uiConfig.icons.inactive);
//textEl.appendChild(iconEl);
}
switch (secondModifier) {
case Modifier.warning: {
if (uiConfig.icons && uiConfig.icons.warning) {
browserUtil.removeClasses(tableRowEl, uiConfig.modifiers.inactive);
browserUtil.addClasses(tableRowEl, uiConfig.modifiers.warning);
let iconEl = document.createElement('i');
browserUtil.addClasses(iconEl, uiConfig.icons.warning);
//textEl.appendChild(iconEl);
}
break;
}
case Modifier.active: {
if (uiConfig.icons && uiConfig.icons.active) {
let iconEl = document.createElement('i');
browserUtil.addClasses(iconEl, uiConfig.icons.active);
//textEl.appendChild(iconEl);
}
break;
}
}
break;
}
case Modifier.warning: {
browserUtil.removeClasses(tableRowEl, uiConfig.modifiers.normal);
browserUtil.addClasses(tableRowEl, uiConfig.modifiers.warning);
break;
}
}
}
return tableRowEl;
}
onDocumentLoaded() {
}
setDisplayElementsForCollectionInContainer(containerEl, collectionName, newState) {
this.currentPage = 0;
this.stateBuffer = newState;
this.containerEl = containerEl;
this.collectionName = collectionName;
this.tableBodyEl = null;
browserUtil.removeAllChildren(containerEl);
// create the table
let tableEl = document.createElement(this.tableConfig.table.type);
browserUtil.addClasses(tableEl, this.tableConfig.table.classes);
browserUtil.addAttributes(tableEl, this.tableConfig.table.attributes);
// create the headers
let tableHeaderEl = document.createElement(this.tableConfig.header.type);
browserUtil.addClasses(tableHeaderEl, this.tableConfig.header.classes);
browserUtil.addAttributes(tableHeaderEl, this.tableConfig.header.attributes);
// create the column headers
this.tableConfig.headerColumns.forEach((header) => {
let thEl = document.createElement(header.element.type);
browserUtil.addClasses(thEl, header.element.classes);
browserUtil.addAttributes(thEl, header.element.attributes);
if (header.element.innerHTML)
thEl.innerHTML = header.element.innerHTML;
if (this.tableConfig.sortableTableHeaders) {
if (header.field) {
const headerFieldId = header.field.id;
if (this.tableConfig.sortableTableHeaders.findIndex((fieldId) => fieldId === headerFieldId) >= 0) {
browserUtil.addClasses(thEl, TabularViewRendererUsingContext.SORTABLE_TH_CLASS);
browserUtil.addAttribute(thEl, { name: DATA_ID_ATTRIBUTE, value: header.field.id });
browserUtil.addAttribute(thEl, { name: TabularViewRendererUsingContext.SORT_DIRECTION_ATTRIBUTE, value: '' + header.sortDirection });
thEl.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
// get the current sort direction
const currentSortDirectionString = thEl.getAttribute(TabularViewRendererUsingContext.SORT_DIRECTION_ATTRIBUTE);
const fieldId = thEl.getAttribute(DATA_ID_ATTRIBUTE);
if (currentSortDirectionString && fieldId) {
const currentSortDirection = parseInt(currentSortDirectionString);
let newSortDirection = CollectionViewSorterDirection.descending;
switch (currentSortDirection) {
case CollectionViewSorterDirection.ascending: {
newSortDirection = CollectionViewSorterDirection.descending;
break;
}
case CollectionViewSorterDirection.descending: {
newSortDirection = CollectionViewSorterDirection.ascending;
break;
}
}
header.sortDirection = newSortDirection;
this.view.applySorter({ majorDirection: newSortDirection, majorFieldId: fieldId });
}
});
}
else {
browserUtil.addClasses(thEl, TabularViewRendererUsingContext.NON_SORTABLE_TH_CLASS);
}
}
else {
browserUtil.addClasses(thEl, TabularViewRendererUsingContext.NON_SORTABLE_TH_CLASS);
}
}
else {
browserUtil.addClasses(thEl, TabularViewRendererUsingContext.NON_SORTABLE_TH_CLASS);
}
tableHeaderEl.appendChild(thEl);
});
// create the action column header (if one)
if (this.tableConfig.actionColumn) {
let thEl = document.createElement(this.tableConfig.actionColumn.element.type);
browserUtil.addClasses(thEl, this.tableConfig.actionColumn.element.classes);
browserUtil.addAttributes(thEl, this.tableConfig.actionColumn.element.attributes);
browserUtil.addClasses(thEl, TabularViewRendererUsingContext.NON_SORTABLE_TH_CLASS);
if (this.tableConfig.actionColumn.element.innerHTML)
thEl.innerHTML = this.tableConfig.actionColumn.element.innerHTML;
tableHeaderEl.appendChild(thEl);
}
tableEl.appendChild(tableHeaderEl);
// create the table body
this.tableBodyEl = document.createElement(this.tableConfig.body.type);
browserUtil.addClasses(this.tableBodyEl, this.tableConfig.body.classes);
browserUtil.addAttributes(this.tableBodyEl, this.tableConfig.body.attributes);
this.setDisplayElementsForCollectionInContainerForNextPage(containerEl, collectionName, newState);
tableEl.appendChild(this.tableBodyEl);
containerEl.appendChild(tableEl);
}
setDisplayElementsForCollectionInContainerForNextPage(containerEl, collectionName, newState) {
paginationLogger(`view ${this.view.getName()}: creating Results`);
logger(newState);
this.currentPage++;
let isLazyLoading = false;
let itemPageSize = this.stateBuffer.length;
if (this.tableConfig && this.tableConfig.lazyLoadPageSize) {
itemPageSize = this.tableConfig.lazyLoadPageSize;
isLazyLoading = true;
}
else {
if (itemPageSize > 50) {
this.tableConfig.lazyLoadPageSize = 50;
itemPageSize = 50;
isLazyLoading = true;
}
}
// remove the previous items from list
if (this.currentPage === 1) {
if (this.tableBodyEl)
browserUtil.removeAllChildren(this.tableBodyEl);
}
const startItemIndex = (this.currentPage - 1) * itemPageSize;
let endItemIndex = (this.currentPage * itemPageSize);
if (this.stateBuffer.length < endItemIndex) {
endItemIndex = this.stateBuffer.length;
}
paginationLogger(`loading items from ${startItemIndex} to ${endItemIndex}`);
// add the new children
let lastChildEl;
for (let index = startItemIndex; index < endItemIndex; index++) {
const item = this.stateBuffer[index];
if (this.tableBodyEl)
lastChildEl = ContextualInformationHelper.getInstance().insertDisplayElementForCollectionItem(this.view, this, this.tableBodyEl, collectionName, item);
}
paginationLogger(`Is lazy loading? ${isLazyLoading} and current page count is ${this.currentPage} with page size ${itemPageSize}`);
if (isLazyLoading && (this.stateBuffer.length > (this.currentPage * itemPageSize))) {
if (lastChildEl) {
this.currentObservedTarget = lastChildEl;
if (this.observer === null) {
paginationLogger(`Creating new observer`);
// put an observer on the last child
this.observer = new IntersectionObserver(this.renderNextPage); //,{root:this.containerEl,threshold:0.0});
}
this.observer.observe(lastChildEl);
}
}
if (!browserUtil.isMobileDevice())
$('[data-toggle="tooltip"]').tooltip();
}
renderNextPage(entries) {
if (entries[0].intersectionRatio <= 0)
return;
paginationLogger('rendering next page');
if (this.observer && this.currentObservedTarget) {
paginationLogger('unobserving current target');
paginationLogger(this.currentObservedTarget);
this.observer.unobserve(this.currentObservedTarget);
this.currentObservedTarget = null;
}
if (this.containerEl && this.collectionName) {
this.setDisplayElementsForCollectionInContainerForNextPage(this.containerEl, this.collectionName, this.stateBuffer);
}
}
}
TabularViewRendererUsingContext.SORT_DIRECTION_ATTRIBUTE = 'sort-direction';
TabularViewRendererUsingContext.SORTABLE_TH_CLASS = 'sortable-th';
TabularViewRendererUsingContext.NON_SORTABLE_TH_CLASS = 'non-sortable-th';
//# sourceMappingURL=TabularViewRendererUsingContext.js.map