Last active
April 20, 2022 08:26
-
-
Save mskoroglu/4080bd06910c2c45e6c8625c5174c6a4 to your computer and use it in GitHub Desktop.
jQuery DataTables with Kotlin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @author Mustafa Köroğlu | |
*/ | |
const val kTableFunction = """ | |
function kTable(table, configURL, cb) { | |
$.post(configURL, function (config) { | |
config.options.ajax.data = JSON.stringify; | |
config.column = function (columnName) { | |
return config.options.columns.filter(function (row) { | |
return row.name === columnName; | |
})[0]; | |
}; | |
config.render = function (columnName, cb) { | |
config.column(columnName).render = function (data, type, row, meta) { | |
var value = data; | |
var cell = row.cells[meta.col]; | |
var cellData = cell.dataAttributes; | |
var rowData = row.dataAttributes; | |
return cb(value, cellData, cell, rowData, row); | |
}; | |
}; | |
var extendedConfig = cb ? cb(config) : config.options; | |
var customCreatedRow = extendedConfig.createdRow; | |
extendedConfig.createdRow = function (td, cellData, rowData, row, col) { | |
if (cellData.dataAttributes) Object.keys(cellData.dataAttributes).forEach(function (key) { | |
$(td).attr('data-' + key, cellData.dataAttributes[key]); | |
}); | |
cellData.cells.forEach(function (cell, i) { | |
if (cell.dataAttributes) Object.keys(cell.dataAttributes).forEach(function (key) { | |
$(row[i]).attr('data-' + key, cell.dataAttributes[key]); | |
}); | |
}); | |
if (customCreatedRow) customCreatedRow.apply(this, arguments); | |
}; | |
table.DataTable(extendedConfig); | |
}); | |
} | |
""" | |
fun <T> dataTable(url: String, ignoreCase: Boolean = true, regex: Boolean = false, init: DataTable<T>.() -> Unit): DataTable<T> { | |
val table = DataTable<T>(url = url, ignoreCase = ignoreCase, regex = regex) | |
table.init() | |
return table | |
} | |
class DataTable<T> internal constructor(private val url: String, private val ignoreCase: Boolean = true, private val regex: Boolean = false) { | |
private val columnBuilders: MutableSet<ColumnBuilder<T>> = mutableSetOf() | |
private val dataAttributes: MutableMap<String, T.() -> Any?> = mutableMapOf() | |
val config: MutableMap<String, Any> | |
get() = mutableMapOf("options" to mutableMapOf( | |
"ajax" to mutableMapOf( | |
"url" to url, | |
"method" to "POST", | |
"contentType" to "application/json" | |
), | |
"serverSide" to true, | |
"processing" to true, | |
"searchDelay" to 500, | |
"columns" to columnBuilders.mapIndexed { i, it -> | |
mutableMapOf( | |
"data" to "cells.$i.value", | |
"name" to it.name, | |
"searchable" to it.isSearchable, | |
"orderable" to it.isSortable | |
) | |
}, | |
"order" to arrayOf(arrayOf(columnBuilders.firstOrNull { it.isSortable }?.index, "asc")) | |
)) | |
fun create(query: Query, collection: Collection<T>): Result { | |
val recordsTotal = collection.size.toLong() | |
var rowSequence: Sequence<Row<T>>? = null | |
// SEARCHING | |
val searchKey = query.search.value | |
if (searchKey.isNotBlank()) { | |
val filteredRows = collection.asSequence().map { item -> | |
val cells = columnBuilders.filter { it.isSearchable }.map { builder -> | |
Cell(builder).apply { | |
searchValue = if (builder.ref === builder.searchRef) { | |
value = builder.ref(item) | |
value | |
} else builder.searchRef(item) | |
} | |
} | |
val searchMatched = cells.map { it.searchValue }.any { | |
if (!regex) it.toString().contains(searchKey, ignoreCase) else try { | |
val options = mutableSetOf<RegexOption>() | |
if (ignoreCase) options.add(RegexOption.IGNORE_CASE) | |
Regex(searchKey, options).matches(it.toString()) | |
} catch (e: Exception) { | |
false | |
} | |
} | |
return@map if (!searchMatched) null else Row(item = item, cells = cells.toMutableList()) | |
} | |
rowSequence = filteredRows.filter { it != null }.map { it!! } | |
} | |
// SORTING | |
val sort = query.order.first() | |
val sortBuilder = columnBuilders.toList()[sort.column] | |
if (sortBuilder.isSortable) { | |
rowSequence = (rowSequence ?: collection.asSequence().map { Row(item = it) }).map { row -> | |
val builder = columnBuilders.find { it === sortBuilder }!! | |
val cellByKey = row.cells.find { it.key == builder.name } | |
val cell = cellByKey ?: Cell(builder) | |
if (!cell.sortValueInvoked) { | |
cell.sortValue = if (builder.ref === builder.sortRef) { | |
if (!cell.valueInvoked) | |
cell.value = builder.ref(row.item) | |
cell.value | |
} else builder.sortRef(row.item) | |
} | |
if (cellByKey == null) row.cells.add(cell) | |
return@map row | |
} | |
val selector: (Row<T>) -> Comparable<Any?> = { | |
@Suppress("UNCHECKED_CAST") | |
it.cells.find { cell -> cell.key == sortBuilder.name }!!.sortValue as Comparable<Any?> | |
} | |
rowSequence = if (sort.dir == "asc") rowSequence.sortedBy(selector) | |
else rowSequence.sortedByDescending(selector) | |
} | |
// PAGING | |
val filteredAndSorted = (rowSequence ?: collection.asSequence().map { Row(item = it) }).toList() | |
val fromIndex = query.start | |
val to = fromIndex + query.length | |
val toIndex = if (to > filteredAndSorted.size) filteredAndSorted.size else to | |
val page = filteredAndSorted.subList(fromIndex, toIndex) | |
// MAPPING | |
val mappedResult = page.map { row -> | |
val item = row.item | |
columnBuilders.forEach { builder -> | |
val cellByKey = row.cells.find { it.key == builder.name } | |
val cell = cellByKey ?: Cell(builder) | |
if (!cell.valueInvoked) cell.value = builder.ref(item) | |
builder.dataAttributes.forEach { cell.dataAttributes[it.key] = it.value(item) } | |
if (cellByKey == null) row.cells.add(cell) | |
} | |
this.dataAttributes.forEach { row.dataAttributes[it.key] = it.value(item) } | |
val rowMap = mutableMapOf<String, Any?>( | |
"cells" to row.cells.sortedBy { it.builder.index }.map { cell -> | |
val cellMap = mutableMapOf("value" to cell.value) | |
if (cell.dataAttributes.isNotEmpty()) | |
cellMap["dataAttributes"] = cell.dataAttributes | |
cellMap | |
} | |
) | |
if (row.dataAttributes.isNotEmpty()) rowMap["dataAttributes"] = row.dataAttributes | |
return@map rowMap | |
} | |
return Result( | |
draw = query.draw, | |
recordsTotal = recordsTotal, | |
recordsFiltered = filteredAndSorted.size.toLong(), | |
data = mappedResult | |
) | |
} | |
fun column(name: String, ref: T.() -> Any?) = ColumnBuilder(columnBuilders.size, name, ref).also { | |
columnBuilders.add(it) | |
} | |
fun computed(name: String, ref: T.() -> Any?) { | |
columnBuilders.add(ColumnBuilder(columnBuilders.size, name, ref)) | |
} | |
fun rowData(init: Data<T>.() -> Unit) { | |
val data = Data<T>() | |
data.init() | |
this.dataAttributes.putAll(data.map) | |
} | |
class Data<T>(internal val map: MutableMap<String, T.() -> Any?> = mutableMapOf()) { | |
infix fun String.to(other: T.() -> Any?) { | |
map[this] = other | |
} | |
} | |
class ColumnBuilder<T>(internal var index: Int, internal val name: String, internal val ref: T.() -> Any?) { | |
internal var isSortable: Boolean = false | |
internal var isSearchable: Boolean = false | |
internal var sortRef: T.() -> Any? = ref | |
internal var searchRef: T.() -> Any? = ref | |
internal val dataAttributes: MutableMap<String, T.() -> Any?> = mutableMapOf() | |
internal fun sort() = this.apply { isSortable = true } | |
internal fun sort(ref: T.() -> Any?) = this.apply { isSortable = true; sortRef = ref } | |
internal fun search() = this.apply { isSearchable = true } | |
internal fun search(ref: T.() -> Any?) = this.apply { isSearchable = true; searchRef = ref } | |
fun data(init: Data<T>.() -> Unit): ColumnBuilder<T> { | |
val data = Data<T>() | |
data.init() | |
this.dataAttributes.putAll(data.map) | |
return this | |
} | |
} | |
private class Row<T>(val item: T, val cells: MutableList<Cell<T>> = mutableListOf(), val dataAttributes: MutableMap<String, Any?> = mutableMapOf()) | |
private class Cell<T>(val builder: ColumnBuilder<T>) { | |
val key: String = builder.name | |
var valueInvoked = false | |
var value: Any? = null | |
set(value) { | |
field = value | |
valueInvoked = true | |
} | |
var searchValue: Any? = null | |
var sortValueInvoked = false | |
var sortValue: Any? = null | |
set(value) { | |
field = value | |
sortValueInvoked = true | |
} | |
val dataAttributes: MutableMap<String, Any?> = mutableMapOf() | |
} | |
} | |
class Query(val draw: Int, val order: List<Order> = emptyList(), val start: Int, val length: Int, val search: Search) { | |
class Order(var column: Int, var dir: String) | |
class Search(var value: String) | |
} | |
class Result(val draw: Int, val recordsTotal: Long, val recordsFiltered: Long, val data: Collection<*>, val error: String? = null) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment