All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.magicwerk.brownies.html.content.htmlTableFormatter.ts Maven / Gradle / Ivy

The newest version!
// HtmlTableFormatter

// Classes

class ColAttr {
	col: JqHtmlElem
	colIndex: number
	colName: string
	colType: string
	sortPos: number = null
	/** null: no sorting, false: ascending, true: descending */
	sortOrder: boolean = null
	filterStr: string = null
	filter: ColFilter = null

	constructor(col: any, colIndex: number) {
		this.col = col		
		this.colIndex = colIndex
		this.colType 
		this.sortPos 
		this.sortOrder 
		this.filterStr 
		this.filter
	}
	
	clearSort() {
		this.sortPos = null
		this.sortOrder = null
	}
	
	clearFilter() {
		this.filterStr = null
		this.filter = null
	}
}

// Simple click:
// - on column without sorting: clear existing sorting, apply sorting to new column
// - on column with sorting: modify existing sorting of column (also if part of multi sort)
// Click with shift/ctrl:
// - on column without sorting: apply sorting to new column
// - on column with sorting: as above
class TableAttr {
	table: JqHtmlElem
	cols: ColAttr[] = []
	numSort: number
	
	constructor(table: any) {
		this.table = table
	}
	
	clearSort() {
		for (let i=0; i colAttr.sortPos) {
						this.cols[i].sortPos--
					}
				}
				colAttr.sortOrder = null
				colAttr.sortPos = null
				this.numSort--
			}				
		}
	}
}


// Functions

$(document).ready(function() {
	activateTables()
})

function activateTables(node?: JqHtmlElem) {
	if (!node) {
		node = $(document.documentElement)
	}
	$(node).find(".activeTable").each(function() { 
		activateTable($(this))
	})
}

function activateTable(table: JqHtmlElem) {
	let ta = new TableAttr(table)
	table.find('tr:nth-child(1) th').each(function(index) {
		let ca = new ColAttr($(this), index)
		ta.cols.push(ca)
		activateCol(ta, ca)
	});
	
	table.find('tr:not(:first)').each(function(index) {
		setDataRowNum($(this), index)
	});
}

function setDataRowNum(tr: JqHtmlElem, index: number) {
	tr.data('rownum', index)
}

function getDataRowNum(tr: JqHtmlElem) {
	return Sort.toNumber(tr.data('rownum'))
}

function activateCol(tabAttr: TableAttr, colAttr: ColAttr) {
	colAttr.colName = getHeaderElem(tabAttr.table, colAttr.colIndex).text()
	colAttr.colType = getColType(tabAttr, colAttr)	
	log2("activateCol " +  colAttr.colIndex + " -> " + colAttr.colType)
	
	// Attach handlers
	colAttr.col.click(function(e) {
		manageSort(tabAttr, colAttr, e.shiftKey || e.ctrlKey)
	})
	colAttr.col.on('contextmenu', e => {
		manageFilter(tabAttr, colAttr)
		e.preventDefault()
	})
}

function getColType(tabAttr: TableAttr, colAttr: ColAttr) {
	let vals = getColValues(tabAttr.table, colAttr.colIndex)
	let type = null
	for (let i=0; i 0
			case Operation.GE: return cmp >= 0
			case Operation.LT: return cmp < 0
			case Operation.LE: return cmp <= 0
			case Operation.RQ: return cmp === 0
			case Operation.NR: return cmp !== 0
			default: assert2(false)
		}
	}
	
	/** Returns operation for specified string, null if not found */
	static getOperation(str: string): Operation {
		switch (str) {
			case "=": return Operation.EQ
			case "!=": return Operation.NE
			case ">": return Operation.GT
			case ">=": return Operation.GE
			case "<": return Operation.LT
			case "<=": return Operation.LE
			case "~": return Operation.RQ
			case "!~": return Operation.NR
			default: assert2(false)
		}
	}
	
	static isRegexOperation(op: Operation) {
		return op === Operation.RQ || op === Operation.NR
	}
}

// Filter column

/** Show dialog to manage filter. */
function manageFilter(tabAttr: TableAttr, colAttr: ColAttr) {
	let helpText = ` 
"String / Number
""
Example:
"abc": value must be equal to 'abc'

Regex:
"~": match regex
"!~": regex does not match
Example:
"~ action": value must match regex 'action'

String / Number:
"=", "!=", ">", ">=", "<", "<="
Example:
"> 10": values must be >10"
` let okFnc = function(text: string) { text = text.trim() colAttr.filterStr = (text !== '') ? text : null if (colAttr.filterStr) { colAttr.filter = createFilter(colAttr, colAttr.filterStr) } else { colAttr.filter = null } removeFilter(colAttr) addFilter(colAttr) let selected = applyFilter(tabAttr) // Show information about selected rows let all = getNumRows(tabAttr.table) let msg = "Selected " + selected + " of " + all + " rows" showInfo(msg) } let title = "Filter " + colAttr.colName let value = (colAttr.filterStr) ? colAttr.filterStr : "" showDialog({ title: title, value: value, help: helpText, ok: okFnc }) } /** Show information in a non-modal dialog which will automatically close */ function showInfo(text: string) { // As there still maybe a dialog open, show the info dialog using setTimeout setTimeout(() => { showDialog({ label: text, autoClose: 1000 }) }, 0 ) } /** Return created filter */ function createFilter(colAttr: ColAttr, str: string): ColFilter { let op: Operation = null let argStr: string = null let match = str.match(/^([~!<>=]+)\s*(.*)$/) if (match) { op = ColFilter.getOperation(match[1]) if (op) { if (ColFilter.isRegexOperation(op)) { if (colAttr.colType !== TYPE_STRING) { op = null } } } if (op) { argStr = match[2] } } if (op == null) { op = Operation.EQ argStr = str } let arg = getValue(colAttr.colType, argStr) return new ColFilter(colAttr.colType, op, arg) } function getValue(type: string, valStr: string): any { if (type === TYPE_NUMBER) { return Sort.toNumber(valStr) } else { return valStr } } /** Apply filter and return number of selected rows */ function applyFilter(tabAttr: TableAttr): number { let rs = getNumRows(tabAttr.table) let selected = 0 for (let r=0; r 0 } function addFilter(colAttr: ColAttr) { if (colAttr.filterStr) { let elem = $("
").text(colAttr.filterStr) colAttr.col.append(elem) } } function removeFilter(colAttr: ColAttr) { colAttr.col.find('.filter').remove() } // Sort column function manageSort(tabAttr: TableAttr, colAttr: ColAttr, modifier: boolean) { log2("manageSort " + colAttr.colIndex + " " + modifier) tabAttr.changeSort(colAttr, modifier) renderSort(tabAttr) applySort(tabAttr) } function renderSort(tabAttr: TableAttr) { for (let i=0; i number)[] = [] add(cmp: (o1: any, o2: any) => number) { this.comparators.push(cmp) } get() { let self = this return function(o1, o2) { for (let i=0; i 1) { text += getNumberText(colAttr.sortPos) } let elem = $("").text(text) // Add sort indicator before filter if there is any // TODO: it would probably be easier to always have .sort and .filter and just make the elements invisible let filterElem = colAttr.col.find('.filter') if (filterElem.length > 0) { filterElem.before(elem) } else { colAttr.col.append(elem) } } function getArrowText(order: boolean) { if (order === false) { return '\u25b2' // arrow up } else if (order === true) { return '\u25bc' // arrow up } else { assert2(false) } } function getNumberText(pos: number) { let number1 = 0x2780 return String.fromCharCode(number1 + pos); } function removeSort(colAttr: ColAttr) { colAttr.col.find('.sort').remove() } // Method for HTML tables function getHeaderElem(table: JqHtmlElem, col: number) { let c = col + 1 // index has base 1 let sel = 'tr:nth-child(1) th:nth-child('+ c +')' return table.find(sel) } function getColNames(table: JqHtmlElem) { let names = [] table.find('tr:nth-child(1) th').each( function(){ names.push( $(this).text() ); }); return names } function getNumCols(table: JqHtmlElem) { let numCols = table.find('tr:nth-child(1) th').length return numCols } function getNumRows(table: JqHtmlElem) { let numRows = table.find('tr').length return numRows - 1 // subtract 1 for header row } function getTableValues(table: JqHtmlElem) { let rows = [] let rs = getNumRows(table) let cs = getNumCols(table) for (let r=0; r(array: T[], f: (e1: T, e2: T) => number, undefFirst: boolean = false): T[] { // Note that array.sort always sorts undefined elements at the end without calling the passed comparsion function. array.sort(f) if (undefFirst) { let index = array.indexOf(undefined) if (index !== -1) { let array2 = array.splice(index, array.length-index).concat(array.splice(0, index)) Sort.assign(array, array2) } } return array } /** * Create function which compares two array by their value at the specified position. * The two values retrieved are compared using objects.compareNatural. * The created function receives the two arrays as input and can be used by calling arrays.sort. */ static createArrayComparator(index: IInteger, desc: boolean = false, nullsFirst: boolean = false) { let f = function(arr1: any[], arr2: any[]): number { let v1 = arr1[index] let v2 = arr2[index] return Sort.compareNatural(v1, v2, desc, nullsFirst) } return f } static compareNatural(any1: any, any2: any, desc: boolean = false, nullsFirst: boolean = true): IInteger { if (Sort.isValue(any1) && Sort.isValue(any2)) { // Compare two valid values if (Sort.isNumber(any1) && Sort.isNumber(any2)) { return Sort.compareNumbers(any1, any2, desc, nullsFirst) } else if (Sort.isString(any1) && Sort.isString(any2)) { return Sort.compareStrings(any1, any2, desc, nullsFirst) } else { let str1 = Sort.toString(any1) let str2 = Sort.toString(any2) return Sort.compareStrings(str1, str2, desc, nullsFirst) } } else { // At least one value is null or undefined return Sort.doCompareNull(any1, any2, desc, nullsFirst) } } static doCompareNull(any1: any, any2: any, desc: boolean = false, nullsFirst: boolean = true) { // At least one value is null or undefined let v1 = (any1 === null) ? 1 : ((any1 === undefined) ? 2 : 0) let v2 = (any2 === null) ? 1 : ((any2 === undefined) ? 2 : 0) if ( nullsFirst ) { // map 1,2 to to -1,-2 v1 = (v1 >= 1) ? -v1 : v1 v2 = (v2 >= 1) ? -v2 : v2 } let n = v1 - v2 if ( desc ) { if (Math.abs(v1) < 1 && Math.abs(v2) < 1) { n = -n } } return n } static compareNumbers(num1: number, num2: number, desc: boolean = false, nullsFirst: boolean = false): IInteger { let n let f1 = Sort.isValue(num1) && isFinite(num1) let f2 = Sort.isValue(num2) && isFinite(num2) if (f1 && f2) { n = num1 - num2 if ( desc ) { n = -n } //log("compareNumbers: {0} vs {1} -> {2}", num1, num2, n) } else { // Order -Infinity, ALL_FINITE_NUMBERS, +Infinity, NaN, null, undefined let n1 = f1 ? 0 : (Number.isNaN(num1) ? 2 : ((num1 === -Infinity) ? -1 : ((num1 === Infinity) ? 1: ((num1 === null) ? 3 : 4)))) let n2 = f2 ? 0 : (Number.isNaN(num2) ? 2 : ((num2 === -Infinity) ? -1 : ((num2 === Infinity) ? 1: ((num2 === null) ? 3 : 4)))) if ( nullsFirst ) { // map 2,3,4 to -4,-3,-2 n1 = (n1 >= 2) ? -n1 : n1 n2 = (n2 >= 2) ? -n2 : n2 } n = n1 - n2 if ( desc ) { if (Math.abs(n1) < 2 && Math.abs(n2) < 2) { n = -n } } //log("compareNumbers: {0} vs {1} -> {2} ({3} vs {4})", num1, num2, n, n1, n2) } return n } static compareStrings(str1: string, str2: string, desc: boolean = false, nullsFirst: boolean = false): IInteger { let n let isVal1 = Sort.isValue( str1 ) let isVal2 = Sort.isValue( str2 ) if (isVal1 && isVal2 ) { let options = undefined // The locale typically already sorts ignoring the case, i.e. a1, A2, a3 //if (caseInsensitive) { // options = { sensitivity: 'base' } //} n = str1.localeCompare( str2, undefined, options ) if ( desc ) { n = -n } } else { n = Sort.doCompareNull(str1, str2, desc, nullsFirst) } return n } static assign( dst: T[], src: T[] ) { dst.length = 0 for ( let i = 0; i < src.length; i++ ) { dst.push( src[i] ) } } static isValue( obj: any ) { return !(obj === null || obj === undefined) } static isNumber( obj: any ) { return Object.prototype.toString.call( obj ) === "[object Number]" } static isString( obj: any ) { return Object.prototype.toString.call( obj ) === "[object String]" } /** * Returns input value converted to a number. * If the input is null or undefined, the input value is returned unchanged. * If the input value cannot be converted to a number, NaN is returned. */ static toNumber( val: any ): number { if ( !this.isValue( val ) ) { return val } return Number( val ) } static toString( val: any ): string { if ( !this.isValue( val ) ) { return val } return String( val ) } static isEmpty( str: string ): boolean { if ( !this.isValue( str ) ) { return true } return str.length === 0 } } function assert2(cond: boolean) { if (!cond) { throw new Error("Assertion failed") } } function log2(msg: any) { console.log(msg); }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy