-
Notifications
You must be signed in to change notification settings - Fork 81
Expand file tree
/
Copy pathcolumns.R
More file actions
665 lines (617 loc) · 22.9 KB
/
columns.R
File metadata and controls
665 lines (617 loc) · 22.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
#' Column definitions
#'
#' Use `colDef()` to customize the columns in a table.
#'
#' @param name Column header name.
#' @param aggregate Aggregate function to use when rows are grouped. The name
#' of a built-in aggregate function or a custom [JS()] aggregate function.
#' Built-in aggregate functions are: `"mean"`, `"sum"`, `"max"`, `"min"`,
#' `"median"`, `"count"`, `"unique"`, and `"frequency"`.
#'
#' To enable row grouping, use the `groupBy` argument in [reactable()].
#' @param sortable Enable sorting? Overrides the table option.
#' @param resizable Enable column resizing? Overrides the table option.
#' @param filterable Enable column filtering? Overrides the table option.
#' @param searchable Enable or disable global table searching for this column.
#' By default, global searching applies to all visible columns. Set this to
#' `FALSE` to exclude a visible column from searching, or `TRUE` to include a
#' hidden column in searching.
#' @param filterMethod Custom filter method to use for column filtering.
#' A [JS()] function that takes an array of row objects, the column ID,
#' and the filter value as arguments, and returns the filtered array of
#' row objects.
#' @param show Show the column?
#'
#' If `FALSE`, this column will be excluded from global table searching by
#' default. To include this hidden column in searching, set `searchable`
#' to `TRUE` in [colDef()].
#' @param defaultSortOrder Default sort order. Either `"asc"` for ascending
#' order or `"desc"` for descending order. Overrides the table option.
#' @param sortNALast Always sort missing values ([NA] or [NaN]) last?
#' @param format Column formatting options. A [colFormat()] object to
#' format all cells, or a named list of [colFormat()] objects to format standard
#' cells (`"cell"`) and aggregated cells (`"aggregated"`) separately.
#' @param cell Custom cell renderer. An R function that takes the cell value,
#' row index, and column name as arguments, or a [JS()] function that takes a
#' cell info object and table state object as arguments.
#' @param grouped Custom grouped cell renderer. A [JS()] function that takes a
#' cell info object and table state object as arguments.
#' @param aggregated Custom aggregated cell renderer. A [JS()] function that takes
#' a cell info object and table state object as arguments.
#' @param header Custom header renderer. An R function that takes the header value
#' and column name as arguments, or a [JS()] function that takes a column
#' object and table state object as arguments.
#' @param footer Footer content or render function. Render functions can be an
#' R function that takes the column values and column name as arguments, or a
#' [JS()] function that takes a column object and table state object as
#' arguments.
#' @param details Additional content to display when expanding a row. An R function
#' that takes the row index and column name as arguments, or a [JS()] function
#' that takes a row info object and table state object as arguments.
#' Cannot be used on a `groupBy` column.
#' @param filterInput Custom filter input or render function. Render functions can
#' be an R function that takes the column values and column name as arguments,
#' or a [JS()] function that takes a column object and table state object as
#' arguments.
#' @param html Render content as HTML? Raw HTML strings are escaped by default.
#' @param na String to display for missing values (i.e. [NA] or [NaN]).
#' By default, missing values are displayed as blank cells.
#' @param rowHeader Mark up cells in this column as row headers?
#'
#' Set this to `TRUE` to help users navigate the table using assistive technologies.
#' When cells are marked up as row headers, assistive technologies will read them
#' aloud while navigating through cells in the table.
#'
#' Cells in the row names column are automatically marked up as row headers.
#' @param minWidth Minimum width of the column in pixels. Defaults to 100.
#' @param maxWidth Maximum width of the column in pixels.
#' @param width Fixed width of the column in pixels. Overrides `minWidth` and `maxWidth`.
#' @param align Horizontal alignment of content in the column. One of
#' `"left"`, `"right"`, `"center"`. By default, all numbers are right-aligned,
#' while all other content is left-aligned.
#' @param vAlign Vertical alignment of content in data cells. One of `"top"`
#' (the default), `"center"`, `"bottom"`.
#' @param headerVAlign Vertical alignment of content in header cells. One of
#' `"top"` (the default), `"center"`, `"bottom"`.
#' @param sticky Make the column sticky when scrolling horizontally? Either
#' `"left"` or `"right"` to make the column stick to the left or right side.
#'
#' If a sticky column is in a column group, all columns in the group will
#' automatically be made sticky, including the column group header.
#'
#' Sticky columns do not work if `fullWidth` is set to `FALSE` in `reactable()`.
#' @param class Additional CSS classes to apply to cells. Can also be an R function
#' that takes the cell value, row index, and column name as arguments, or a [JS()]
#' function that takes a row info object, column object, and table state object
#' as arguments.
#'
#' Note that R functions cannot apply classes to aggregated cells.
#' @param style Inline styles to apply to cells. A named list or character string.
#' Can also be an R function that takes the cell value and row index as arguments,
#' or a [JS()] function that takes a row info object, column object, and
#' table state object as arguments.
#'
#' Note that R functions cannot apply styles to aggregated cells.
#' If `style` is a named list, property names should be camelCased.
#' @param headerClass Additional CSS classes to apply to the header.
#' @param headerStyle Inline styles to apply to the header. A named list or
#' character string.
#'
#' Note that if `headerStyle` is a named list, property names should be camelCased.
#' @param footerClass Additional CSS classes to apply to the footer.
#' @param footerStyle Inline styles to apply to the footer. A named list or
#' character string.
#'
#' Note that if `footerStyle` is a named list, property names should be camelCased.
#' @return A column definition object that can be used to customize columns
#' in `reactable()`.
#'
#' @examples
#' reactable(
#' iris,
#' columns = list(
#' Sepal.Length = colDef(name = "Sepal Length"),
#' Sepal.Width = colDef(filterable = TRUE),
#' Petal.Length = colDef(show = FALSE),
#' Petal.Width = colDef(defaultSortOrder = "desc")
#' )
#' )
#'
#' @export
colDef <- function(
name = NULL,
aggregate = NULL,
sortable = NULL,
resizable = NULL,
filterable = NULL,
searchable = NULL,
filterMethod = NULL,
show = TRUE,
defaultSortOrder = NULL,
sortNALast = FALSE,
format = NULL,
cell = NULL,
grouped = NULL,
aggregated = NULL,
header = NULL,
footer = NULL,
details = NULL,
filterInput = NULL,
html = FALSE,
na = "",
rowHeader = FALSE,
minWidth = 100,
maxWidth = NULL,
width = NULL,
align = NULL,
vAlign = NULL,
headerVAlign = NULL,
sticky = NULL,
class = NULL,
style = NULL,
headerClass = NULL,
headerStyle = NULL,
footerClass = NULL,
footerStyle = NULL
) {
if (!is.null(name) && !is.character(name)) {
stop("`name` must be a character string")
}
if (!is.null(aggregate)) {
if (is.character(aggregate) && !is.JS(aggregate)) {
aggregators <- c("mean", "sum", "max", "min", "median", "count",
"unique", "frequency")
if (!aggregate %in% aggregators) {
stop("`aggregate` must be a valid aggregate function")
}
} else if (!is.JS(aggregate)) {
stop("`aggregate` must be a character string or JS function")
}
}
if (!is.null(sortable) && !is.logical(sortable)) {
stop("`sortable` must be TRUE or FALSE")
}
if (!is.null(resizable) && !is.logical(resizable)) {
stop("`resizable` must be TRUE or FALSE")
}
if (!is.null(filterable) && !is.logical(filterable)) {
stop("`filterable` must be TRUE or FALSE")
}
if (!is.null(searchable) && !is.logical(searchable)) {
stop("`searchable` must be TRUE or FALSE")
}
if (!is.null(filterMethod) && !is.JS(filterMethod)) {
stop('`filterMethod` must be a JS function')
}
if (!is.null(show) && !is.logical(show)) {
stop("`show` must be TRUE or FALSE")
}
if (!is.null(defaultSortOrder) && !isSortOrder(defaultSortOrder)) {
stop('`defaultSortOrder` must be "asc" or "desc"')
}
if (!is.null(sortNALast) && !is.logical(sortNALast)) {
stop("`sortNALast` must be TRUE or FALSE")
}
if (!is.null(format)) {
if (!is.colFormat(format) && !isNamedList(format)) {
stop('`format` must be a column formatting option set or named list')
}
if (is.colFormat(format)) {
format <- list(cell = format, aggregated = format)
}
if (any(!names(format) %in% c("cell", "aggregated"))) {
stop('`format` must have names "cell" or "aggregated"')
}
for (opts in format) {
if (!is.colFormat(opts)) {
stop("`format` must be a list of column formatting options")
}
}
}
if (!is.null(cell) && !is.JS(cell) && !is.function(cell)) {
stop("`cell` renderer must be an R function or JS function")
}
if (!is.null(grouped) && !is.JS(grouped)) {
stop("`grouped` renderer must be a JS function")
}
if (!is.null(aggregated) && !is.JS(aggregated)) {
stop("`aggregated` renderer must be a JS function")
}
if (!is.null(details) && !is.function(details) && !is.JS(details) && !is.list(details)) {
stop("`details` renderer must be an R function or JS function")
}
if (!is.null(html) && !is.logical(html)) {
stop("`html` must be TRUE or FALSE")
}
if (!is.null(na) && !is.character(na)) {
stop("`na` must be a character string")
}
if (!is.null(rowHeader) && !is.logical(rowHeader)) {
stop("`rowHeader` must be TRUE or FALSE")
}
if (!is.null(minWidth) && !is.numeric(minWidth)) {
stop("`minWidth` must be numeric")
}
if (!is.null(maxWidth) && !is.numeric(maxWidth)) {
stop("`maxWidth` must be numeric")
}
if (!is.null(width) && !is.numeric(width)) {
stop("`width` must be numeric")
}
if (!is.null(align)) {
if (!isTRUE(align %in% c("left", "right", "center"))) {
stop('`align` must be one of "left", "right", "center"')
}
}
if (!is.null(vAlign)) {
if (!isTRUE(vAlign %in% c("top", "center", "bottom"))) {
stop('`vAlign` must be one of "top", "center", "bottom"')
}
}
if (!is.null(headerVAlign)) {
if (!isTRUE(headerVAlign %in% c("top", "center", "bottom"))) {
stop('`headerVAlign` must be one of "top", "center", "bottom"')
}
}
if (!is.null(sticky)) {
if (!isTRUE(sticky %in% c("left", "right"))) {
stop('`sticky` must be "left" or "right"')
}
}
if (!is.null(class) && !is.character(class) && !is.JS(class) && !is.function(class)) {
stop("`class` must be a character string, JS function, or R function")
}
if (!is.null(style) && !isNamedList(style) && !is.character(style) &&
!is.JS(style) && !is.function(style)) {
stop("`style` must be a named list, character string, JS function, or R function")
}
if (!is.null(headerClass) && !is.character(headerClass)) {
stop("`headerClass` must be a character string")
}
if (!is.null(headerStyle) && !isNamedList(headerStyle) && !is.character(headerStyle)) {
stop("`headerStyle` must be a named list or character string")
}
if (!is.null(footerClass) && !is.character(footerClass)) {
stop("`footerClass` must be a character string")
}
if (!is.null(footerStyle) && !isNamedList(footerStyle) && !is.character(footerStyle)) {
stop("`footerStyle` must be a named list or character string")
}
# If an arg with a non-NULL default value wasn't specified by the user, filter
# them out so they can take on the default from a default column definition.
userArgs <- names(match.call())[-1]
structure(
filterNulls(list(
name = name,
aggregate = aggregate,
sortable = sortable,
resizable = resizable,
filterable = filterable,
searchable = searchable,
filterMethod = filterMethod,
show = if ("show" %in% userArgs) show,
defaultSortDesc = if (!is.null(defaultSortOrder)) isDescOrder(defaultSortOrder),
sortNALast = if ("sortNALast" %in% userArgs) sortNALast,
format = format,
cell = cell,
grouped = grouped,
aggregated = aggregated,
header = header,
footer = footer,
details = details,
filterInput = filterInput,
html = if ("html" %in% userArgs) html,
na = if ("na" %in% userArgs) na,
rowHeader = if ("rowHeader" %in% userArgs) rowHeader,
minWidth = if ("minWidth" %in% userArgs) minWidth,
maxWidth = maxWidth,
width = width,
align = align,
vAlign = vAlign,
headerVAlign = headerVAlign,
sticky = sticky,
className = class,
style = if (is.function(style) || is.JS(style)) style else asReactStyle(style),
headerClassName = headerClass,
headerStyle = asReactStyle(headerStyle),
footerClassName = footerClass,
footerStyle = asReactStyle(footerStyle)
)),
class = "colDef"
)
}
is.colDef <- function(x) {
inherits(x, "colDef")
}
isSortOrder <- function(x) {
is.character(x) && x %in% c("asc", "desc")
}
isDescOrder <- function(x) {
is.character(x) && x == "desc"
}
#' Column group definitions
#'
#' Use `colGroup()` to create column groups in a table.
#'
#' @param name Column group header name.
#' @param columns Character vector of column names in the group.
#' @param header Custom header renderer. An R function that takes the header value
#' as an argument, or a [JS()] function that takes a column object and
#' table state object as arguments.
#' @param html Render header content as HTML? Raw HTML strings are escaped by default.
#' @param align Horizontal alignment of content in the column group header. One of
#' `"left"`, `"right"`, `"center"` (the default).
#' @param headerVAlign Vertical alignment of content in the column group header. One of
#' `"top"` (the default), `"center"`, `"bottom"`.
#' @param sticky Make the column group sticky when scrolling horizontally? Either
#' `"left"` or `"right"` to make the column group stick to the left or right side.
#'
#' If a column group is sticky, all columns in the group will automatically
#' be made sticky.
#' @param headerClass Additional CSS classes to apply to the header.
#' @param headerStyle Inline styles to apply to the header. A named list or
#' character string.
#'
#' Note that if `headerStyle` is a named list, property names should be camelCased.
#' @return A column group definition object that can be used to create column
#' groups in `reactable()`.
#'
#' @examples
#' reactable(
#' iris,
#' columns = list(
#' Sepal.Length = colDef(name = "Length"),
#' Sepal.Width = colDef(name = "Width"),
#' Petal.Length = colDef(name = "Length"),
#' Petal.Width = colDef(name = "Width")
#' ),
#' columnGroups = list(
#' colGroup(name = "Sepal", columns = c("Sepal.Length", "Sepal.Width")),
#' colGroup(name = "Petal", columns = c("Petal.Length", "Petal.Width"))
#' )
#' )
#'
#' @export
colGroup <- function(
name = NULL,
columns = NULL,
header = NULL,
html = FALSE,
align = NULL,
headerVAlign = NULL,
sticky = NULL,
headerClass = NULL,
headerStyle = NULL
) {
if (!is.null(name) && !is.character(name)) {
stop("`name` must be a character string")
}
if (!is.null(columns)) {
if (!is.character(columns)) {
stop("`columns` must be a character vector")
} else {
# Ensure column IDs are serialized as an array
columns <- as.list(columns)
}
}
# If an arg with a non-NULL default value wasn't specified by the user, filter
# them out so they can take on the default from a default column definition.
userArgs <- names(match.call())[-1]
args <- filterNulls(list(
name = name,
header = header,
html = if ("html" %in% userArgs) html,
align = align,
headerVAlign = headerVAlign,
sticky = sticky,
headerClass = headerClass,
headerStyle = headerStyle
))
group <- tryCatch({
do.call(colDef, args)
}, error = function(e) e)
if (inherits(group, "error")) {
stop(group$message)
}
group$columns <- columns
structure(group, class = "colGroup")
}
is.colGroup <- function(x) {
inherits(x, "colGroup")
}
#' Column formatting options
#'
#' Use `colFormat()` to add data formatting to a column.
#'
#' @param prefix Prefix string.
#' @param suffix Suffix string.
#' @param digits Number of decimal digits to use for numbers.
#' @param separators Whether to use grouping separators for numbers, such as
#' thousands separators or thousand/lakh/crore separators. The format is
#' locale-dependent.
#' @param percent Format number as a percentage? The format is locale-dependent.
#' @param currency Currency format. An ISO 4217 currency code such as `"USD"`
#' for the US dollar, `"EUR"` for the euro, or `"CNY"` for the Chinese RMB.
#' The format is locale-dependent.
#' @param datetime Format as a locale-dependent date-time?
#' @param date Format as a locale-dependent date?
#' @param time Format as a locale-dependent time?
#' @param hour12 Whether to use 12-hour time (`TRUE`) or 24-hour time (`FALSE`).
#' The default time convention is locale-dependent.
#' @param locales Locales to use for number, date, time, and currency formatting.
#' A character vector of BCP 47 language tags, such as `"en-US"` for English
#' (United States), `"hi"` for Hindi, or `"sv-SE"` for Swedish (Sweden).
#' Defaults to the locale of the user's browser.
#'
#' Multiple locales may be specified to provide a fallback language in case
#' a locale is unsupported. When multiple locales are specified, the first
#' supported locale will be used.
#'
#' See a list of [common BCP 47 language tags](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085406-a698-4e12-9d4d-c3b0ee3dbc4a)
#' for reference.
#' @return A column format object that can be used to customize data formatting
#' in `colDef()`.
#'
#' @seealso Custom cell rendering in [colDef()] to customize data formatting
#' beyond what the built-in formatters provide.
#'
#' @examples
#' data <- data.frame(
#' price_USD = c(123456.56, 132, 5650.12),
#' price_INR = c(350, 23208.552, 1773156.4),
#' number_FR = c(123456.56, 132, 5650.12),
#' temp = c(22, NA, 31),
#' percent = c(0.9525556, 0.5, 0.112),
#' date = as.Date(c("2019-01-02", "2019-03-15", "2019-09-22"))
#' )
#'
#' reactable(data, columns = list(
#' price_USD = colDef(format = colFormat(prefix = "$", separators = TRUE, digits = 2)),
#' price_INR = colDef(format = colFormat(currency = "INR", separators = TRUE, locales = "hi-IN")),
#' number_FR = colDef(format = colFormat(locales = "fr-FR")),
#' temp = colDef(format = colFormat(suffix = " \u00b0C")),
#' percent = colDef(format = colFormat(percent = TRUE, digits = 1)),
#' date = colDef(format = colFormat(date = TRUE, locales = "en-GB"))
#' ))
#'
#' # Date formatting
#' datetimes <- as.POSIXct(c("2019-01-02 3:22:15", "2019-03-15 09:15:55", "2019-09-22 14:20:00"))
#' data <- data.frame(
#' datetime = datetimes,
#' date = datetimes,
#' time = datetimes,
#' time_24h = datetimes,
#' datetime_pt_BR = datetimes
#' )
#'
#' reactable(data, columns = list(
#' datetime = colDef(format = colFormat(datetime = TRUE)),
#' date = colDef(format = colFormat(date = TRUE)),
#' time = colDef(format = colFormat(time = TRUE)),
#' time_24h = colDef(format = colFormat(time = TRUE, hour12 = FALSE)),
#' datetime_pt_BR = colDef(format = colFormat(datetime = TRUE, locales = "pt-BR"))
#' ))
#'
#' # Currency formatting
#' data <- data.frame(
#' USD = c(12.12, 2141.213, 0.42, 1.55, 34414),
#' EUR = c(10.68, 1884.27, 0.37, 1.36, 30284.32),
#' INR = c(861.07, 152122.48, 29.84, 110, 2444942.63),
#' JPY = c(1280, 226144, 44.36, 164, 3634634.61),
#' MAD = c(115.78, 20453.94, 4.01, 15, 328739.73)
#' )
#'
#' reactable(data, columns = list(
#' USD = colDef(
#' format = colFormat(currency = "USD", separators = TRUE, locales = "en-US")
#' ),
#' EUR = colDef(
#' format = colFormat(currency = "EUR", separators = TRUE, locales = "de-DE")
#' ),
#' INR = colDef(
#' format = colFormat(currency = "INR", separators = TRUE, locales = "hi-IN")
#' ),
#' JPY = colDef(
#' format = colFormat(currency = "JPY", separators = TRUE, locales = "ja-JP")
#' ),
#' MAD = colDef(
#' format = colFormat(currency = "MAD", separators = TRUE, locales = "ar-MA")
#' )
#' ))
#'
#' # Formatting aggregated cells
#' data <- data.frame(
#' States = state.name,
#' Region = state.region,
#' Area = state.area
#' )
#'
#' reactable(
#' data,
#' groupBy = "Region",
#' columns = list(
#' States = colDef(
#' aggregate = "count",
#' format = list(
#' aggregated = colFormat(suffix = " states")
#' )
#' ),
#' Area = colDef(
#' aggregate = "sum",
#' format = colFormat(suffix = " mi\u00b2", separators = TRUE)
#' )
#' )
#' )
#'
#' @export
colFormat <- function(prefix = NULL, suffix = NULL, digits = NULL,
separators = FALSE, percent = FALSE, currency = NULL,
datetime = FALSE, date = FALSE, time = FALSE, hour12 = NULL,
locales = NULL) {
if (!is.null(prefix) && !is.character(prefix)) {
stop("`prefix` must be a character string")
}
if (!is.null(suffix) && !is.character(suffix)) {
stop("`suffix` must be a character string")
}
if (!is.null(digits)) {
if (!is.numeric(digits) || digits < 0 || digits > 18) {
stop("`digits` must be a number between 0 and 18")
}
digits <- as.integer(digits)
}
if (!is.logical(separators)) {
stop("`separators` must be TRUE or FALSE")
}
if (!is.logical(percent)) {
stop("`percent` must be TRUE or FALSE")
}
if (!is.null(currency) && !is.character(currency)) {
stop("`currency` must be a character string")
}
if (!is.logical(datetime)) {
stop("`datetime` must be TRUE or FALSE")
}
if (!is.logical(date)) {
stop("`date` must be TRUE or FALSE")
}
if (!is.logical(time)) {
stop("`time` must be TRUE or FALSE")
}
if (!is.null(hour12) && !is.logical(hour12)) {
stop("`hour12` must be TRUE or FALSE")
}
if (!is.null(locales) && !is.character(locales)) {
stop("`locales` must be a character string")
}
options <- list(
prefix = prefix,
suffix = suffix,
digits = digits,
separators = if (separators) TRUE,
percent = if (percent) TRUE,
currency = currency,
datetime = if (datetime) TRUE,
date = if (date) TRUE,
time = if (time) TRUE,
hour12 = hour12,
locales = locales
)
options <- filterNulls(options)
structure(options, class = "colFormat")
}
is.colFormat <- function(x) {
inherits(x, "colFormat")
}
colType <- function(x) {
if (is.null(x)) {
return(NULL)
}
if (is.numeric(x)) {
return("numeric")
}
if (is.Date(x) || is.POSIXct(x) || is.POSIXlt(x)) {
return("Date")
}
class(x)
}