diff --git a/DESCRIPTION b/DESCRIPTION index 1fab395..ce520c8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: AVSDevR.ShinyInputs Title: Provides Additional Inputs for Shiny -Version: 0.0.0.9000 +Version: 0.0.0.9001 Authors@R: person("Craig", "Williams", , "craig@avsdev.uk", role = c("aut", "cre")) Description: Provides additional inputs which can be used in shiny @@ -9,3 +9,7 @@ License: GPL (>= 3) Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.3 +Imports: + htmltools, + lubridate, + shiny diff --git a/NAMESPACE b/NAMESPACE index 6ae9268..9306a31 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,2 +1,10 @@ # Generated by roxygen2: do not edit by hand +export(betterDateInput) +export(betterDateRangeInput) +export(numericButtonInput) +export(togglableTextInput) +export(updateBetterDateInput) +export(updateBetterDateRangeInput) +export(updateNumericButtonInput) +export(updateTogglableTextInput) diff --git a/R/betterDateInput.R b/R/betterDateInput.R new file mode 100644 index 0000000..7b74ed7 --- /dev/null +++ b/R/betterDateInput.R @@ -0,0 +1,105 @@ + +parseDateTime <- function(dtVec) { + lapply( + dtVec, + lubridate::parse_date_time, + c("%Y-%m-%d", "%d/%m/%Y", "%y-%m-%d", "%d/%m/%y", "%y%m%d", "%d%m%y"), + exact = TRUE + ) +} + +formatDates <- function(value, min, max) { + dates <- list(min = min, max = max) + dates <- parseDateTime(dates) + value <- parseDateTime(value) + if (length(dates$min) != 0 && length(value) != 0) { + value[lubridate::ymd(value) < lubridate::ymd(dates$min)] <- dates$min + } + if (length(dates$max) != 0 && length(value) != 0) { + value[lubridate::ymd(value) > lubridate::ymd(dates$max)] <- dates$max + } + dates <- lapply(dates, format, "%Y-%m-%d") + value <- lapply(value, format, "%Y-%m-%d") + dates <- lapply(dates, function(.x) { + if (length(.x) == 0) NULL else .x + }) + value <- lapply(value, function(.x) { + if (length(.x) == 0) NULL else .x + }) + c(dates, list(value = value)) +} + +#' @export +betterDateInput <- function( + inputId, label, value = NULL, min = NULL, max = NULL, + display_format = "yyyy-mm-dd", startview = "month", weekstart = 0, + language = "en", width = NULL, multiple = FALSE, autoclose = !multiple, ... +) { + value <- shiny::restoreInput(id = inputId, default = value) + + dates <- formatDates(value, min, max) + + htmltools::attachDependencies( + shiny::div( + id = inputId, + class = "shiny-betterDate-input form-group shiny-input-container", + style = htmltools::css( + width = shiny::validateCssUnit(width) + ), + shiny:::shinyInputLabel(inputId, label), + shiny::div( + class = "input-betterDate input-group", + `data-min-date` = dates$min, + `data-max-date` = dates$max, + `data-initial-date` = dates$value, + shiny::tags$input( + class = "form-control", + type = "text", + name = "value", + autocomplete = "off", + `aria-labelledby` = paste0(inputId, "-label"), + `data-date-language` = language, + `data-date-week-start` = weekstart, + `data-date-format` = display_format, + `data-date-start-view` = startview, + `data-date-autoclose` = if (autoclose) "true" else "false", + `data-date-multidate` = if (multiple) "true" else "false", + title = paste("Date format:", display_format) + ), + ) + ), + betterDateInputDependency() + ) +} + +#' @export +updateBetterDateInput <- function( + session, inputId, label = NULL, min = NULL, max = NULL, value = NULL +) { + dates <- formatDates(value, min, max) + message <- list( + label = label, value = shiny:::dropNulls(dates$value), + min = dates$min, max = dates$max, + id = inputId + ) + message <- shiny:::dropNulls(message) + session$sendInputMessage(inputId, message) +} + +betterDateInputDependency <- function() { + if (getOption("shiny.minified", TRUE)) { + js <- c("js/betterDateInput.min.js") + } else { + js <- c("js/betterDateInput.js") + } + + c( + shiny:::datePickerDependency(), + list(htmltools::htmlDependency( + "betterDateInput", + as.character(utils::packageVersion("AVSDevR.ShinyInputs")), + c(file = system.file(package = "AVSDevR.ShinyInputs")), + script = js, + )) + ) +} diff --git a/R/betterDateRangeInput.R b/R/betterDateRangeInput.R new file mode 100644 index 0000000..c3e4012 --- /dev/null +++ b/R/betterDateRangeInput.R @@ -0,0 +1,139 @@ + +parseDateTime <- function(dtVec) { + lapply( + dtVec, + lubridate::parse_date_time, + c("%Y-%m-%d", "%d/%m/%Y", "%y-%m-%d", "%d/%m/%y", "%y%m%d", "%d%m%y"), + exact = TRUE + ) +} + +formatRangeDates <- function(start, end, min, max) { + orig_dates <- list(start = start, end = end, min = min, max = max) + + dates <- parseDateTime(orig_dates) + + if (length(dates$min) != 0 + && length(dates$start) != 0 + && lubridate::ymd(dates$start) < lubridate::ymd(dates$min) + ) { + dates$start <- dates$min + } + if (length(dates$max) != 0 + && length(dates$end) != 0 + && lubridate::ymd(dates$end) > lubridate::ymd(dates$max) + ) { + dates$end <- dates$max + } + + dates <- lapply(names(orig_dates), function(dn) { + if (is.null(orig_dates[[dn]])) { + return(NULL) + } + d <- format(dates[[dn]], "%Y-%m-%d") + if (length(d) == 0) { + d <- character(0) + } + d + }) + names(dates) <- names(orig_dates) + + dates +} + +#' @export +betterDateRangeInput <- function( + inputId, label, start = NULL, end = NULL, min = NULL, max = NULL, + display_format = "yyyy-mm-dd", startview = "month", weekstart = 0, + language = "en", separator = " to ", width = NULL, autoclose = TRUE, + autonext = TRUE, ... +) { + restored <- shiny::restoreInput( + id = inputId, + default = list(start = start, end = end) + ) + start <- restored$start + end <- restored$end + + dates <- formatRangeDates(start, end, min, max) + + htmltools::attachDependencies( + shiny::div( + id = inputId, + class = "shiny-dateRange-input form-group", + style = htmltools::css( + width = shiny::validateCssUnit(width) + ), + shiny:::shinyInputLabel(inputId, label), + shiny::div( + class = "input-daterange input-group", + `data-date-language` = language, + `data-date-week-start` = weekstart, + `data-date-format` = display_format, + `data-date-start-view` = startview, + `data-min-date` = dates$min, + `data-max-date` = dates$max, + `data-initial-start-date` = dates$start, + `data-initial-end-date` = dates$end, + `data-date-autoclose` = if (autoclose) "true" else "false", + `data-autonext` = if (autonext) "true" else "false", + shiny::tags$input( + class = "form-control", + type = "text", + name = paste0(inputId, "_start"), + autocomplete = "off", + `aria-labelledby` = paste0(inputId, "-label"), + title = paste("Date format:", display_format) + ), + shiny::span( + class = "input-group-addon", + shiny::span(class = "input-group-text", separator) + ), + shiny::tags$input( + class = "form-control", + type = "text", + name = paste0(inputId, "_end"), + autocomplete = "off", + `aria-labelledby` = paste0(inputId, "-label"), + title = paste("Date format:", display_format) + ) + ) + ), + betterDateRangeInputDependency() + ) +} + +#' @export +updateBetterDateRangeInput <- function( + session, inputId, label = NULL, min = NULL, max = NULL, start = NULL, + end = NULL +) { + dates <- formatRangeDates(start, end, min, max) + message <- list( + label = label, + value = shiny:::dropNulls(list(start = dates$start, end = dates$end)), + min = dates$min, + max = dates$max, + id = inputId + ) + message <- shiny:::dropNulls(message) + session$sendInputMessage(inputId, message) +} + +betterDateRangeInputDependency <- function() { + if (getOption("shiny.minified", TRUE)) { + js <- c("js/betterDateRangeInput.min.js") + } else { + js <- c("js/betterDateRangeInput.js") + } + + c( + shiny:::datePickerDependency(), + list(htmltools::htmlDependency( + "betterDateRangeInput", + as.character(utils::packageVersion("AVSDevR.ShinyInputs")), + c(file = system.file(package = "AVSDevR.ShinyInputs")), + script = js, + )) + ) +} diff --git a/R/numericButtonInput.R b/R/numericButtonInput.R new file mode 100644 index 0000000..61f3e6b --- /dev/null +++ b/R/numericButtonInput.R @@ -0,0 +1,87 @@ + +#' @export +numericButtonInput <- function( + inputId, label, buttonText = NULL, buttonIcon = NULL, min = NULL, max = NULL, + step = NULL, value, ... +) { + value <- shiny::restoreInput(id = inputId, default = value) + + btnTag <- shiny::tags$button( + id = paste0(inputId, "_click"), + type = "button", + class = "numericButton-button btn btn-default action-button", + list(shiny:::validateIcon(buttonIcon), buttonText) + ) + numTag <- shiny::tags$input( + id = paste0(inputId, "_value"), + type = "number", + class = "numericButton-numeric form-control", + autocomplete = "off", + value = shiny:::formatNoSci(value) + ) + + if (!is.null(min)) { + numTag$attribs$min <- min + } + if (!is.null(max)) { + numTag$attribs$max <- max + } + if (!is.null(step)) { + numTag$attribs$step <- step + } + + htmltools::attachDependencies( + shiny::div( + id = inputId, + class = "form-group shiny-input-container shiny-input-numericButton", + `aria-labelledby` = paste0(inputId, "-label"), + shiny::tags$label( + class = "control-label", + id = paste0(inputId, "-label"), + `for` = paste0(inputId, "_value"), + label + ), + shiny::div( + class = "input-group", + numTag, + shiny::span( + class = "input-group-btn", + btnTag + ) + ) + ), + numericButtonInputDependency() + ) +} + +#' @export +updateNumericButtonInput <- function( + session, inputId, label = NULL, min = NULL, max = NULL, step = NULL, + value = NULL +) { + message <- list( + label = label, + value = shiny:::formatNoSci(value), + min = shiny:::formatNoSci(min), + max = shiny:::formatNoSci(max), + step = shiny:::formatNoSci(step), + id = inputId + ) + message <- shiny:::dropNulls(message) + session$sendInputMessage(inputId, message) +} + +numericButtonInputDependency <- function() { + if (getOption("shiny.minified", TRUE)) { + js <- c("js/numericButtonInput.min.js") + } else { + js <- c("js/numericButtonInput.js") + } + + list(htmltools::htmlDependency( + "numericButtonInput", + as.character(utils::packageVersion("AVSDevR.ShinyInputs")), + c(file = system.file(package = "AVSDevR.ShinyInputs")), + script = js, + )) +} diff --git a/R/toggleableTextInput.R b/R/toggleableTextInput.R new file mode 100644 index 0000000..b3442da --- /dev/null +++ b/R/toggleableTextInput.R @@ -0,0 +1,92 @@ + +#' @export +togglableTextInput <- function( + inputId, label, checked = FALSE, text = NULL, placeholder = NULL, + enableWhenChecked = TRUE, ... +) { + value <- shiny::restoreInput( + id = inputId, + default = list(checked = checked, text = text) + ) + + checkTag <- shiny::tags$input( + id = paste0(inputId, "Check"), + type = "checkbox", + class = "togglableText-check", + autocomplete = "off" + ) + textTag <- shiny::tags$input( + id = paste0(inputId, "Text"), + type = "text", + class = "togglableText-text form-control", + autocomplete = "off", + placeholder = placeholder, + value = value$text + ) + + if (!is.null(value$checked) && value$checked) { + checkTag$attribs$checked <- "checked" + if (!is.null(enableWhenChecked) && !enableWhenChecked) { + textTag$attribs$disabled <- "disabled" + } + } else { + if (is.null(enableWhenChecked) || enableWhenChecked) { + textTag$attribs$disabled <- "disabled" + } + } + + htmltools::attachDependencies( + shiny::div( + id = inputId, + class = "form-group shiny-input-container shiny-input-togglableText", + `aria-labelledby` = paste0(inputId, "-label"), + `aria-enable-on-check` = ifelse( + is.null(enableWhenChecked) | enableWhenChecked, 1, 0 + ), + shiny::tags$label( + class = "control-label", + id = paste0(inputId, "-label"), + `for` = paste0(inputId, "Text"), + label + ), + shiny::div( + class = "input-group", + shiny::span( + class = "input-group-addon", + checkTag + ), + textTag + ) + ), + togglableTextInputDependency() + ) +} + +#' @export +updateTogglableTextInput <- function( + session, inputId, label = NULL, checked = NULL, text = NULL, + placeholder = NULL +) { + message <- list( + label = label, value = list(checked = checked, text = text), + placeholder = placeholder, + id = inputId + ) + message <- shiny:::dropNulls(message) + session$sendInputMessage(inputId, message) +} + +togglableTextInputDependency <- function() { + if (getOption("shiny.minified", TRUE)) { + js <- c("js/togglableTextInput.min.js") + } else { + js <- c("js/togglableTextInput.js") + } + + list(htmltools::htmlDependency( + "togglableTextInput", + as.character(utils::packageVersion("AVSDevR.ShinyInputs")), + c(file = system.file(package = "AVSDevR.ShinyInputs")), + script = js, + )) +} diff --git a/inst/js/betterDateInput.js b/inst/js/betterDateInput.js new file mode 100644 index 0000000..c014768 --- /dev/null +++ b/inst/js/betterDateInput.js @@ -0,0 +1,163 @@ +var dateInputBinding = new Shiny.InputBinding(); +$.extend(dateInputBinding, { + _newDate: function(date) { + if (date instanceof Date) + return date; + var d = Date.parse(date); + if (isNaN(d)) + return null; + return new Date(d); + }, + _setMin: function(el, date) { + if (date == null) { + $(el).bsDatepicker('setStartDate', null); + return; + } + var parsedDate = this._newDate(date); + if (parsedDate === null) + return; + var curValue = $(el).bsDatepicker('getUTCDate'); + $(el).bsDatepicker('setStartDate', this._utcDateAsLocal(parsedDate)); + if (parsedDate && curValue && parsedDate.getTime() > curValue.getTime()) { + $(el).bsDatepicker('clearDates'); + } else { + $(el).bsDatepicker('setUTCDate', curValue); + } + }, + _setMax: function(el, date) { + if (date == null) { + $(el).bsDatepicker('setEndDate', null); + return; + } + var parsedDate = this._newDate(date); + if (parsedDate === null) + return; + var curValue = $(el).bsDatepicker('getUTCDate'); + $(el).bsDatepicker('setEndDate', this._utcDateAsLocal(parsedDate)); + if (parsedDate && curValue && parsedDate.getTime() < curValue.getTime()) { + $(el).bsDatepicker('clearDates'); + } else { + $(el).bsDatepicker('setUTCDate', curValue); + } + }, + _dateAsUTC: function(date) { + return new Date(date.getTime() - date.getTimezoneOffset() * 6e4); + }, + _utcDateAsLocal: function(date) { + return new Date(date.getTime() + date.getTimezoneOffset() * 6e4); + }, + _padZeros: function(n, digits) { + var str = n.toString(); + while (str.length < digits) { + str = "0" + str; + } + return str; + }, + _formatDateUTC: function(date) { + if (date instanceof Date) { + return date.getUTCFullYear() + "-" + this._padZeros(date.getUTCMonth() + 1, 2) + "-" + this._padZeros(date.getUTCDate(), 2); + } else { + return null; + } + }, + + + find: function(scope) { + return $(scope).find('.shiny-betterDate-input'); + }, + getId: function(el) { + return $(el).attr('id'); + }, + getValue: function(el) { + var $el = $(el); + var $input = $el.find('input').eq(0); + + var value = null; + if ($input.data('date-multidate') == true) { + value = $input.bsDatepicker('getUTCDates'); + if (value.length > 0) { + value = value.map(this._formatDateUTC.bind(this)); + } + } else { + value = $input.bsDatepicker('getUTCDate'); + value = this._formatDateUTC(value); + } + + return value; + }, + setValue: function(el, value) { + var $el = $(el); + var $input = $el.find('input').eq(0); + + if (value !== void 0) { + if (value === null || (Array.isArray(value) && value.length == 0)) { + $input.val('').bsDatepicker('update'); + } else { + if ($input.data('date-multidate') == true && Array.isArray(value) && value.length > 1) { + var newValues = value.map(this._newDate.bind(this)); + $input.bsDatepicker('setUTCDates', newValues); + } else { + var newValue = this._newDate(value); + $input.bsDatepicker('setUTCDate', newValue); + } + } + } + }, + subscribe: function(el, callback) { + $(el).on('change', function(event) { + callback(); + }); + }, + unsubscribe: function(el) { + $(el).off('change'); + }, + getState: function(el) { + return { + value: this.getValue(el) + }; + }, + receiveMessage: function(el, data) { + var $el = $(el); + var $input = $el.find('input').eq(0); + + if (data.hasOwnProperty('label')) { + $(el).find('label[for="' + el.id + 'Text"]').text(data.label); + } + + if (data.hasOwnProperty('min')) { + this._setMin($input[0], data.min); + } + + if (data.hasOwnProperty('max')) { + this._setMax($input[0], data.max); + } + + if (data.hasOwnProperty('value')) { + this.setValue(el, data.value); + } + + // if (data.hasOwnProperty('disabled')) { + // } + + $el.trigger('change'); + }, + initialize: function initialize(el) { + var $el = $(el); + var $mainInput = $el.find('.input-betterDate').eq(0); + var $input = $el.find('input').eq(0) + + $input.bsDatepicker(); + + var value = $mainInput.data('initial-date'); + this.setValue(el, value); + + this._setMin($input[0], $mainInput.data('min-date')); + this._setMax($input[0], $mainInput.data('max-date')); + + $input.bsDatepicker().on('changeDate', function(event) { + $el.trigger('change'); + }) + } +}); + +Shiny.inputBindings.register(dateInputBinding, 'shinyExtra.dateInputBinding'); diff --git a/inst/js/betterDateInput.js.map b/inst/js/betterDateInput.js.map new file mode 100644 index 0000000..f7bc490 --- /dev/null +++ b/inst/js/betterDateInput.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["betterDateInput.js"],"names":["dateInputBinding","Shiny","InputBinding","$","extend","_newDate","date","Date","d","parse","isNaN","_setMin","el","bsDatepicker","parsedDate","this","curValue","_utcDateAsLocal","getTime","_setMax","_dateAsUTC","getTimezoneOffset","_padZeros","n","digits","str","toString","length","_formatDateUTC","getUTCFullYear","getUTCMonth","getUTCDate","find","scope","getId","attr","getValue","$el","$input","eq","value","data","map","bind","setValue","Array","isArray","val","newValues","newValue","subscribe","callback","on","event","unsubscribe","off","getState","receiveMessage","hasOwnProperty","id","text","label","min","max","trigger","initialize","$mainInput","inputBindings","register"],"mappings":"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"} \ No newline at end of file diff --git a/inst/js/betterDateInput.min.js b/inst/js/betterDateInput.min.js new file mode 100644 index 0000000..010a214 --- /dev/null +++ b/inst/js/betterDateInput.min.js @@ -0,0 +1 @@ +var dateInputBinding=new Shiny.InputBinding;$.extend(dateInputBinding,{_newDate:function(e){if(e instanceof Date)return e;var t=Date.parse(e);if(isNaN(t))return null;return new Date(t)},_setMin:function(e,t){if(t==null){$(e).bsDatepicker("setStartDate",null);return}var a=this._newDate(t);if(a===null)return;var i=$(e).bsDatepicker("getUTCDate");$(e).bsDatepicker("setStartDate",this._utcDateAsLocal(a));if(a&&i&&a.getTime()>i.getTime()){$(e).bsDatepicker("clearDates")}else{$(e).bsDatepicker("setUTCDate",i)}},_setMax:function(e,t){if(t==null){$(e).bsDatepicker("setEndDate",null);return}var a=this._newDate(t);if(a===null)return;var i=$(e).bsDatepicker("getUTCDate");$(e).bsDatepicker("setEndDate",this._utcDateAsLocal(a));if(a&&i&&a.getTime()0){i=i.map(this._formatDateUTC.bind(this))}}else{i=a.bsDatepicker("getUTCDate");i=this._formatDateUTC(i)}return i},setValue:function(e,t){var a=$(e);var i=a.find("input").eq(0);if(t!==void 0){if(t===null||Array.isArray(t)&&t.length==0){i.val("").bsDatepicker("update")}else{if(i.data("date-multidate")==true&&Array.isArray(t)&&t.length>1){var n=t.map(this._newDate.bind(this));i.bsDatepicker("setUTCDates",n)}else{var r=this._newDate(t);i.bsDatepicker("setUTCDate",r)}}}},subscribe:function(e,t){$(e).on("change",function(e){t()})},unsubscribe:function(e){$(e).off("change")},getState:function(e){return{value:this.getValue(e)}},receiveMessage:function(e,t){var a=$(e);var i=a.find("input").eq(0);if(t.hasOwnProperty("label")){$(e).find('label[for="'+e.id+'Text"]').text(t.label)}if(t.hasOwnProperty("min")){this._setMin(i[0],t.min)}if(t.hasOwnProperty("max")){this._setMax(i[0],t.max)}if(t.hasOwnProperty("value")){this.setValue(e,t.value)}a.trigger("change")},initialize:function e(t){var a=$(t);var i=a.find(".input-betterDate").eq(0);var n=a.find("input").eq(0);n.bsDatepicker();var r=i.data("initial-date");this.setValue(t,r);this._setMin(n[0],i.data("min-date"));this._setMax(n[0],i.data("max-date"));n.bsDatepicker().on("changeDate",function(e){a.trigger("change")})}});Shiny.inputBindings.register(dateInputBinding,"shinyExtra.dateInputBinding"); \ No newline at end of file diff --git a/inst/js/betterDateInput.min.js.map b/inst/js/betterDateInput.min.js.map new file mode 100644 index 0000000..a565f46 --- /dev/null +++ b/inst/js/betterDateInput.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["betterDateInput.js"],"names":["dateInputBinding","Shiny","InputBinding","$","extend","_newDate","date","Date","d","parse","isNaN","_setMin","el","bsDatepicker","parsedDate","this","curValue","_utcDateAsLocal","getTime","_setMax","_dateAsUTC","getTimezoneOffset","_padZeros","n","digits","str","toString","length","_formatDateUTC","getUTCFullYear","getUTCMonth","getUTCDate","find","scope","getId","attr","getValue","$el","$input","eq","value","data","map","bind","setValue","Array","isArray","val","newValues","newValue","subscribe","callback","on","event","unsubscribe","off","getState","receiveMessage","hasOwnProperty","id","text","label","min","max","trigger","initialize","$mainInput","inputBindings","register"],"mappings":"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"} \ No newline at end of file diff --git a/inst/js/betterDateRangeInput.js b/inst/js/betterDateRangeInput.js new file mode 100644 index 0000000..3c114e1 --- /dev/null +++ b/inst/js/betterDateRangeInput.js @@ -0,0 +1,233 @@ +var dateRangeInputBinding = new Shiny.InputBinding(); +$.extend(dateRangeInputBinding, { + _newDate: function(date) { + if (date instanceof Date) + return date; + var d = Date.parse(date); + if (isNaN(d)) + return null; + return new Date(d); + }, + _setMin: function(el, date) { + var $el = $(el); + var $mainInput = $el.find('.input-daterange').eq(0); + + var autonext = false; + if ($mainInput.data('autonext') == true) { + $mainInput.data('autonext', false); + autonext = true; + } + + if (date == null) { + $el.bsDatepicker('setStartDate', null); + $mainInput.data('autonext', autonext); + return; + } + + var parsedDate = this._newDate(date); + if (parsedDate === null) { + $mainInput.data('autonext', autonext); + return; + } + + var curValue = $(el).bsDatepicker('getUTCDate'); + $el.bsDatepicker('setStartDate', this._utcDateAsLocal(parsedDate)); + if (parsedDate && curValue && parsedDate.getTime() > curValue.getTime()) { + $el.bsDatepicker('clearDates'); + } else { + $el.bsDatepicker('setUTCDate', curValue); + } + + $mainInput.data('autonext', autonext); + }, + _setMax: function(el, date) { + var $el = $(el); + var $mainInput = $el.find('.input-daterange').eq(0); + + var autonext = false; + if ($mainInput.data('autonext') == true) { + $mainInput.data('autonext', false); + autonext = true; + } + + if (date == null) { + $el.bsDatepicker('setEndDate', null); + $mainInput.data('autonext', autonext); + return; + } + + var parsedDate = this._newDate(date); + if (parsedDate === null) { + $mainInput.data('autonext', autonext); + return; + } + + var curValue = $el.bsDatepicker('getUTCDate'); + $el.bsDatepicker('setEndDate', this._utcDateAsLocal(parsedDate)); + if (parsedDate && curValue && parsedDate.getTime() < curValue.getTime()) { + $el.bsDatepicker('clearDates'); + } else { + $el.bsDatepicker('setUTCDate', curValue); + } + + $mainInput.data('autonext', autonext); + }, + _dateAsUTC: function(date) { + return new Date(date.getTime() - date.getTimezoneOffset() * 6e4); + }, + _utcDateAsLocal: function(date) { + return new Date(date.getTime() + date.getTimezoneOffset() * 6e4); + }, + _padZeros: function(n, digits) { + var str = n.toString(); + while (str.length < digits) { + str = "0" + str; + } + return str; + }, + _formatDateUTC: function(date) { + if (date instanceof Date) { + return date.getUTCFullYear() + "-" + this._padZeros(date.getUTCMonth() + 1, 2) + "-" + this._padZeros(date.getUTCDate(), 2); + } else { + return null; + } + }, + + + find: function(scope) { + return $(scope).find('.shiny-dateRange-input'); + }, + getId: function(el) { + return $(el).attr('id'); + }, + getValue: function(el) { + var $inputs = $(el).find('input'); + var start = $inputs.eq(0).bsDatepicker('getUTCDate'); + var end = $inputs.eq(1).bsDatepicker('getUTCDate'); + return { + start: this._formatDateUTC(start), + end: this._formatDateUTC(end) + }; + }, + setValue: function(el, value) { + if (!(value instanceof Object)) { + return; + } + + var $el = $(el); + var $mainInput = $el.find('.input-daterange').eq(0); + + var autonext = false; + if ($mainInput.data('autonext') == true) { + $mainInput.data('autonext', false); + autonext = true; + } + + var $inputs = $el.find('input'); + if (value.start !== void 0) { + if (value.start === null || value.start.length == 0) { + $inputs.eq(0).val('').bsDatepicker('clearDates'); + } else { + var start = this._newDate(value.start); + $inputs.eq(0).bsDatepicker('setUTCDate', start); + } + } + if (value.end !== void 0) { + if (value.end === null || value.end.length == 0) { + $inputs.eq(1).val('').bsDatepicker('clearDates'); + } else { + var end = this._newDate(value.end); + $inputs.eq(1).bsDatepicker('setUTCDate', end); + } + } + + $mainInput.data('autonext', autonext); + }, + subscribe: function(el, callback) { + $(el).on('change', function(event) { + callback(); + }); + }, + unsubscribe: function(el) { + $(el).off('change'); + }, + getState: function(el) { + return { + value: this.getValue(el) + }; + }, + receiveMessage: function(el, data) { + var $el = $(el); + var $inputs = $el.find('input'); + var $startinput = $inputs.eq(0); + var $endinput = $inputs.eq(1); + + if (data.hasOwnProperty('label')) { + $(el).find('label[for="' + el.id + 'Text"]').text(data.label); + } + + if (data.hasOwnProperty('min')) { + this._setMin($startinput[0], data.min); + this._setMin($endinput[0], data.min); + } + + if (data.hasOwnProperty('max')) { + this._setMax($startinput[0], data.max); + this._setMax($endinput[0], data.max); + } + + if (data.hasOwnProperty('value')) { + this.setValue(el, data.value); + } + + // if (data.hasOwnProperty('disabled')) { + // } + + $(el).trigger('change'); + }, + initialize: function initialize(el) { + var $el = $(el); + var $mainInput = $el.find('.input-daterange').eq(0); + var $inputs = $el.find('input'); + var $startinput = $inputs.eq(0); + var $endinput = $inputs.eq(1); + + var start = $mainInput.data('initial-start-date'); + var end = $mainInput.data('initial-end-date'); + + $el.find('.input-daterange').eq(0).bsDatepicker(); + + this.setValue(el, { + start: start, + end: end + }); + + this._setMin($startinput[0], $mainInput.data('min-date')); + this._setMin($endinput[0], $mainInput.data('min-date')); + + this._setMax($startinput[0], $mainInput.data('max-date')); + this._setMax($endinput[0], $mainInput.data('max-date')); + + $startinput.on('focus', function(event) { + $(this).select(); + }); + $endinput.on('focus', function(event) { + $(this).select(); + }); + + $startinput.bsDatepicker().on('changeDate', function(event) { + if ($mainInput.data('autonext') == true && $startinput.bsDatepicker('getUTCDate')) { + $startinput.bsDatepicker('hide'); + $endinput.focus(); + $endinput.select(); + $endinput.bsDatepicker('show'); + } + $el.trigger('change'); + }) + $endinput.bsDatepicker().on('changeDate', function(event) { + $el.trigger('change'); + }) + } +}); + +Shiny.inputBindings.register(dateRangeInputBinding, 'shinyExtra.dateRangeInputBinding'); diff --git a/inst/js/betterDateRangeInput.min.js b/inst/js/betterDateRangeInput.min.js new file mode 100644 index 0000000..6f0e600 --- /dev/null +++ b/inst/js/betterDateRangeInput.min.js @@ -0,0 +1 @@ +var dateRangeInputBinding=new Shiny.InputBinding;$.extend(dateRangeInputBinding,{_newDate:function(e){if(e instanceof Date)return e;var t=Date.parse(e);if(isNaN(t))return null;return new Date(t)},_setMin:function(e,t){var a=$(e);var n=a.find(".input-daterange").eq(0);var i=false;if(n.data("autonext")==true){n.data("autonext",false);i=true}if(t==null){a.bsDatepicker("setStartDate",null);n.data("autonext",i);return}var r=this._newDate(t);if(r===null){n.data("autonext",i);return}var s=$(e).bsDatepicker("getUTCDate");a.bsDatepicker("setStartDate",this._utcDateAsLocal(r));if(r&&s&&r.getTime()>s.getTime()){a.bsDatepicker("clearDates")}else{a.bsDatepicker("setUTCDate",s)}n.data("autonext",i)},_setMax:function(e,t){var a=$(e);var n=a.find(".input-daterange").eq(0);var i=false;if(n.data("autonext")==true){n.data("autonext",false);i=true}if(t==null){a.bsDatepicker("setEndDate",null);n.data("autonext",i);return}var r=this._newDate(t);if(r===null){n.data("autonext",i);return}var s=a.bsDatepicker("getUTCDate");a.bsDatepicker("setEndDate",this._utcDateAsLocal(r));if(r&&s&&r.getTime()