de.bund.bfr.knime.js/src/js/app/app.ui.js
/*
version: 1.0.0
author: sascha obermüller
date: 04.12.2020
*/
class APPUI {
constructor( settings ) {
let O = this;
O._debug = true;
// defaults
O._opts = $.extend( true, {}, {
on : {
afterInit : null
}
}, settings );
}
get opts() {
return this._opts;
}
set opts( settings ) {
this._opts = $.extend( true, {}, this.opts, settings );
}
/**
* CREATE TABLE HEAD
* @param {array} settings
*/
_createTableHead ( settings ) {
// thead
let $thead = $( '<thead></thead>' );
if( settings ) {
// thead id
settings.id ? $thead.attr( 'id', settings.id ) : null;
// create th cols
if( settings.cols ) {
$.each( settings.cols, ( i, col ) => {
let $th = $( '<th></th>' )
.appendTo( $thead );
// th attributes
col.label ? $th.html( '<span>'+ col.label +'</span>' ) : null;
col.id ? $th.attr( 'id', col.id ) : null; // id
col.classes && col.classes.th ? $th.addClass( col.classes.th ) : null; // classes
col.field ? $th.attr( 'data-field', col.field ) : null; // bs table / data-field identifier
col.sortable ? $th.attr( 'data-sortable', col.sortable ) : null; // bs table / data-sortable
col.switchable ? $th.attr( 'data-switchable', col.switchable ) : null; // bs table / data-switchable
col.sorter ? $th.attr( 'data-sorter', col.sorter ) : null; // bs table / data-sorter function
// th custom attributes
if( col.attribute && col.attributes.th ) {
$.each( col.attributes.th, ( attr, val ) => {
$th.attr( attr, val );
} );
}
} );
$thead.wrapInner( '<tr></tr>' );
}
}
return $thead;
}
/**
* CREATE MODAL
* @param {array} settings
* @param {jquery selector/object} $container: append to this
*/
_createModal ( settings, $container ) {
let O = this;
_log( 'UI / _createModal' );
return new APPModal( settings, $container );
}
/**
* CREATE LOADER
* create page loader
* @param {array} settings
* @param {jquery selector/object} $container: append to this
*/
_createLoader ( settings, $container ) {
let O = this;
_log( 'UI / _createLoader' );
let loader = {};
loader._$el = $( '<div class="loader loading"></div>' )
.appendTo( $container );
// optional classes
settings.classes ? loader._$el.addClass( settings.classes ) : null;
loader._setState = ( state ) => {
_log( 'UI / loader._setState : '+ state );
state ? loader._$el.addClass( 'loading' ) : loader._$el.removeClass( 'loading' );
};
return loader;
}
/**
* CREATE ALERT
* create alert and place it into $container
* @param {array} settings
* @param {jquery selector/object} $container: append to this
*/
_createAlert ( msg, settings, $container ) {
let O = this;
_log( 'UI / _createAlert' );
let $alert = $( '<div class="alert fade">'+ msg +'</div>' )
.appendTo( $container );
// alert type
settings.type ? $alert.addClass( 'alert-'+ settings.type ) : $alert.addClass( 'alert-info' ); // bs type: primary, secondary, success, danger, warning, info
settings.state ? $alert.addClass( settings.state ) : $alert.addClass( 'show' ); // hide or show
settings.classes ? $alert.addClass( settings.classes ) : null;
// dismissable
if ( settings.dismissable ) {
$alert.addClass( 'alert-dismissable' );
// close button
$alert.append( '<button type="button" class="close close-sm" data-dismiss="alert" aria-label="Close"><i class="feather icon-x" aria-hidden="true"></i></button>' );
}
return $alert;
}
_precision(param) {
let step = 1;
// add case if parameter is integer (no decimal point)
let decimals = param.includes(".") ? param.substring(param.indexOf('.') + 1).length : 0;
for ( let j = 0; j < decimals; j++ ) {
step = step / 10;
}
return step;
}
/**
* POPULATE SELECT
*
* @param {element} select: dom element
* @param {array} options: array of possible values
*/
_populateSelect ( $select, options ) {
let O = this;
if( $select ) {
options.forEach( entry => {
$select.append( '<option value="'+ entry +'">'+ entry +'</option>' );
} );
}
}
/**
* POPULATE SELECT BY ID
*
* @param {string} selectID: element id
* @param {array} options: array of possible values
*/
_populateSelectByID ( selectID, options ) {
let O = this;
let $select = $( selectID );
O._populateSelect( $select, options );
}
/**
* CREATE TOOLTIPS
* initialize tooltips on all elements with data-attribute [data-tooltip]
* @param {string/jquery selector} container: dom-element that contains the elements to init
*/
_initTooltips ( container ) {
let O = this;
_log( 'UI / _initTooltips' );
let $elems = $( container ).length > 0 ? $( container ).find( '[data-tooltip]' ) : $( '[data-tooltip]' );
$elems.each( ( i, el ) => {
let $el = $( el );
// create tooltips
$el.tooltip( {
offset : 10
} );
} );
}
/**
* INIT TOGGLE TD
* adds a collapsable container on element <td data-td-collapse>, when td's content height is higher than defined min-height
* must be initiated before bs table init
* @param {string/jquery selector} container: dom-element that contains the elements to init
*/
_initTdCollapse ( $table ) {
let O = this;
_log( 'UI / _initTdCollapse' );
let minH = 100;
let $tds = $table.find( 'td[data-td-collapse="true"]' );
$tds.each( ( i, td ) => {
let $td = $( td ); // td
$td.wrapInner( '<div></div>' );
// check for content higher than min height
if( $td.children().outerHeight() > minH ) {
// create unique id
let collapseId = 'tdCollapse'+ jQuery.now();
// wrap inner with collapse container
$td.wrapInner( '<div id="'+ collapseId +'" class="collapse td-collapse"></div>' );
// create toggle
let $collapseToggle = $( '<a href="#" class="td-collapse-toggle collapsed" data-target="#'+ collapseId +'" data-toggle="collapse" aria-expanded="false" aria-controls="'+ collapseId +'"></a>' ).appendTo( $td );
// create collapse
$( '#'+ collapseId ).collapse( {
toggle: false
} )
}
} );
}
/**
* INIT SELECT2
* initialize select2 lib on element <select data-sel2 …>
* @param {string/jquery selector} container: dom-element that contains the elements to init
*/
_initSelect2 ( container ) {
let O = this;
_log( 'UI / _initSelect2' );
let $elems = $( container ).length > 0 ? $( container ).find( 'select[data-sel2]' ) : $( 'select[data-sel2]' );
$elems.each( ( i, el ) => {
let $el = $( el ); // select
let select2Defaults = {
dropdownParent : $el.parent(),
dropdownAutoWidth : false,
// minimumResultsForSearch : Infinity,
width : '100%'
};
// check for settings by attributes
// select size
if( el.hasAttribute( 'data-sel2-size' ) && $el.data( 'sel2-size' ) == 'sm' ||
$el.hasClass( 'form-control-sm' ) ||
$el.hasClass( 'custom-select-sm' ) ) {
select2Defaults.selectionCssClass = 'select2-selection--sm';
select2Defaults.dropdownCssClass = 'select2-dropdown--sm';
}
// check allow clear attr
if( el.hasAttribute( 'data-allow-clear' ) && $el.data( 'allow-clear' ) == true ) {
select2Defaults.selectionCssClass += ' select2-selection--clear';
}
// check custom max height attr
if( el.hasAttribute( 'data-sel2-max-height' ) ) {
select2Defaults.selectionCssClass += ' select2-selection--max-height';
}
// check custom choice single row
if( el.hasAttribute( 'data-sel2-choice-single-row' ) ) {
select2Defaults.selectionCssClass += ' select2-selection--choice-single-row';
}
// check max selection length attr
if( el.hasAttribute( 'data-maximum-selection-length' ) && $el.data( 'maximum-selection-length' ) == '1' ) {
select2Defaults.selectionCssClass += ' select2-selection--max-sel-1';
}
// create select2
$el.select2( select2Defaults );
// $( window ).on( 'resize', function() {
// $el.select2( select2Defaults );
// } );
} );
}
/**
* INIT CLEAR
* initialize clear function
* data-clear attribute should contain targets for clear as jquery selector
* @param {string/jquery selector} container: dom-element that contains the elements to init
*/
_initClear ( container ) {
let O = this;
_log( 'UI / _initClear' );
let $elems = $( container ).length > 0 ? $( container ).find( '[data-clear]' ) : $( '[data-clear]' );
$elems.each( ( i, el ) => {
let $clear = $( el ); // button or a
// set clear targets
$clear.targets = $clear.data( 'clear' );
if( $( $clear.targets ) ) {
$clear.state = false;
$( $clear.targets ).on( 'change keyup', ( event ) => {
$clear.state = false;
// check all target's state
$.each( $( $clear.targets ), ( j, target ) => {
if( $( target ).val().length > 0 ) {
$clear.addClass( 'visible' );
$clear.state = true;
}
} );
if( $clear.state ) {
$clear.addClass( 'visible' );
}
else {
$clear.removeClass( 'visible' );
}
} );
// add event to clear
$clear.on( 'click', ( event ) => {
// iterate all targets and reset
$.each( $( $clear.targets ), ( j, target ) => {
let $target = $( target );
if( $target.is( 'select') ) {
$target.val( '' ).trigger('change');
}
else if( $target.is( 'input') ) {
$target.val( '' ).trigger('change');
}
} );
// hide clear
$clear.state = false;
$clear.removeClass( 'visible' );
} );
}
else {
$clear.remove();
}
} );
}
/**
* INIT TOUCHSSPIN
* initialize touchspin lib on element <input type="text" data-touchspin …>
* @param {string/jquery selector} container: dom-element that contains the elements to init
*/
_initTouchspin ( container ) {
let O = this;
_log( 'UI / _initTouchspin' );
let $elems = $( container ).length > 0 ? $( container ).find( 'input[data-touchspin]' ) : $( 'input[data-touchspin]' );
$elems.each( ( i, el ) => {
let $el = $( el ); // input
let touchspinDefaults = {
buttondown_class : "btn btn-outline-secondary",
buttonup_class : "btn btn-outline-secondary",
decimals : 0,
initval : 0,
mousewheel : true,
min : null,
forcestepdivisibility : 'none',
step : 1
};
// check for settings by attributes
// min & max
if( el.hasAttribute( 'data-touchspin-min' ) ) {
touchspinDefaults.min = $el.data( 'touchspin-min' );
}
if( el.hasAttribute( 'data-touchspin-max' ) ) {
touchspinDefaults.max = $el.data( 'touchspin-max' );
}
// step
if( el.hasAttribute( 'data-touchspin-step' ) ) {
touchspinDefaults.step = $el.data( 'touchspin-step' );
}
// decimals
if( el.hasAttribute( 'data-touchspin-decimals' ) ) {
touchspinDefaults.decimals = $el.data( 'touchspin-decimals' );
}
// initial value
if( el.hasAttribute( 'data-touchspin-initval' ) ) {
touchspinDefaults.initval = $el.data( 'touchspin-initval' );
}
// prefix & postfix
if( el.hasAttribute( 'data-touchspin-prefix' ) ) {
touchspinDefaults.prefix = $el.data( 'touchspin-prefix' );
}
if( el.hasAttribute( 'data-touchspin-postfix' ) ) {
touchspinDefaults.postfix = $el.data( 'touchspin-postfix' );
}
_log( touchspinDefaults );
// create touchspin
$el.TouchSpin( touchspinDefaults );
$el.on('change', function (event) {
if(isNaN(event.currentTarget.value)){
$el.trigger('touchspin.destroy');
}else{
$el.TouchSpin( touchspinDefaults );
}
});
} );
}
/**
* INIT DATEPICKER
* initialize jquery.datepicker lib on element <input type="text" data-datepicker …>
* @param {array} settings: setting for datepicker
* @param {string/jquery selector} container: dom-element that contains the elements to init
*/
_initDatepicker ( container ) {
let O = this;
_log( 'UI / _initDatepicker' );
let $elems = $( container ).length > 0 ? $( container ).find( '[data-datepicker]' ) : $( '[data-datepicker]' );
$elems.each( ( i, el ) => {
let $el = $( el ); // input or input group
// create datepicker
$el.datepicker( {
format : {
toDisplay: ( date, format, language ) => {
let d = new Date( date );
let day = d.getDate();
let month = d.getMonth();
let year = d.getFullYear();
return year +'-'+ month +'-'+ day;
},
toValue: ( date, format, language ) => {
return date;
}
},
todayHighlight : true,
} );
} );
}
/**
* INIT RANGESLIDER
* initialize ion.rangeslider lib on element <input type="text" data-rangeslider …>
* @param {string/jquery selector} container: dom-element that contains the elements to init
*/
_initRangeslider ( container ) {
let O = this;
_log( 'UI / _initRangeslider' );
let $elems = $( container ).length > 0 ? $( container ).find( '[data-rangeslider]' ) : $( '[data-rangeslider]' );
$elems.each( ( i, el ) => {
let $el = $( el ); // input
let rangesliderDefaults = {
drag_interval : true,
};
// check if inputs for rangeslider are set and exist as el
// for double slider
if( el.hasAttribute( 'data-type' ) && $el.data( 'type' ) == 'double' ) {
// from input
if( el.hasAttribute( 'data-control-double-from' ) && $( $el.data( 'control-double-from' ) ).length > 0 ) {
$el.$inputDoubleFrom = $( $el.data( 'control-double-from' ) );
$el.$inputDoubleFrom.on( 'change', ( event ) => {
let min = $el.data( 'rangeslider' ).result.min;
let to = $el.data( 'rangeslider' ).result.to;
let val = $el.$inputDoubleFrom.prop( 'value' );
// validate
if ( val < min ) {
val = min;
}
else if ( val > to ) {
val = to;
}
$el.data( 'ionRangeSlider' ).update( {
from : val
} );
$el.$inputDoubleFrom.val( val );
} );
}
// to input
if( el.hasAttribute( 'data-control-double-to' ) && $( $el.data( 'control-double-to' ) ).length > 0 ) {
$el.$inputDoubleTo = $( $el.data( 'control-double-to' ) );
$el.$inputDoubleTo.on( 'change', ( event ) => {
let max = $el.data( 'rangeslider' ).result.max;
let from = $el.data( 'rangeslider' ).result.from;
let val = $el.$inputDoubleTo.prop( 'value' );
// validate
if ( val < from ) {
val = from;
}
else if ( val > max ) {
val = max;
}
$el.data( 'rangeslider' ).update( {
to : val
} );
$el.$inputDoubleTo.val( val );
} );
}
// if inputs for from/to, ad update routines
if( $el.$inputDoubleFrom || $el.$inputDoubleTo ) {
rangesliderDefaults = $.merge( {
drag_interval : false,
onStart : ( data ) => { $el.updateInputs( data ); },
onChange : ( data ) => { $el.updateInputs( data ); },
onFinish : ( data ) => { $el.updateInputs( data ); }
}, rangesliderDefaults );
$el.updateInputs = ( data ) => {
from = data.from;
to = data.to;
$el.$inputDoubleFrom.prop( 'value', from );
$el.$inputDoubleTo.prop( 'value', to );
};
}
}
// for single slider
else {
// to do
// from input
if( el.hasAttribute( 'data-control-single' ) && $( $el.data( 'control-single' ) ).length > 0 ) {
$el.$inputSingle = $( $el.data( 'control-single' ) );
$el.$inputSingle.on( 'change', ( event ) => {
let min = $el.data( 'rangeslider' ).result.min;
let max = $el.data( 'rangeslider' ).result.max;
let val = $el.$inputSingle.prop( 'value' );
// validate
if ( val < min ) {
val = min;
}
else if ( val > max ) {
val = max;
}
$el.data( 'rangeslider' ).update( {
from : val,
step: O._precision(val)
} );
$el.$inputSingle.val( val );
} );
}
// if inputs for from/to, ad update routines
if( $el.$inputSingle ) {
rangesliderDefaults = $.merge( {
drag_interval : false,
onStart : ( data ) => { $el.updateInputs( data ); },
onChange : ( data ) => { $el.updateInputs( data ); },
onFinish : ( data ) => { $el.updateInputs( data ); }
}, rangesliderDefaults );
$el.updateInputs = ( data ) => {
from = data.from;
$el.$inputSingle.prop( 'value', from );
};
}
}
// create datepicker
$el.ionRangeSlider( rangesliderDefaults );
$el.data( 'rangeslider', $el.data( 'ionRangeSlider' ) );
} );
}
/**
* INIT FORM VALIDATION
* initialize validation on forms with data-atribute [data-validate]
* @param {string/jquery selector} container: dom-element that contains the elements to init
*/
_initFormValidation ( container ) {
let O = this;
_log( 'UI / _initFormValidation' );
let $elems = $( container ).length > 0 ? $( container ).find( '[data-validate]' ) : $( '[data-validate]' );
$elems.each( ( i, el ) => {
let $el = $( el ); // form
let $validations = $el.find( '.validate-me' );
let validation = Array.prototype.filter.call( $elems, ( form ) => {
form.addEventListener( 'submit', ( event ) => {
if ( form.checkValidity() === false ) {
e.preventDefault();
e.stopPropagation();
}
// added validation class to all form-groups in need of validation
$validations.each( ( j, val ) => {
$( val ).addClass( 'was-validated' );
} );
}, false );
} );
} );
}
/**
* INIT FORMS
* combined initialization external lib items
* - touchspin
* - select2
* - datepicker
* - ion rangeslider
*/
_initFormItems ( container ) {
let O = this;
_log( 'UI / _initFormItems' );
O._initClear( container );
O._initTouchspin( container );
O._initSelect2( container );
O._initDatepicker( container );
O._initRangeslider( container );
};
/**
* INIT All
* combined initialization of all external lib items
* - touchspin
* - select2
* - datepicker
* - ion rangeslider
*/
_initAll () {
let O = this;
_log( 'UI / _initAll' );
O._initClear();
O._initTouchspin();
O._initSelect2();
O._initDatepicker();
O._initRangeslider();
O._initFormValidation();
};
}
var _appUI = _appUI || new APPUI();