R/params.R

##' Build a simple AnalysisPageParam
##'
##' Build a simple AnalysisPageParam. These include mainly parameters that can be rendered simply as HTML <input> tags.
##' @title simple.param
##' @param name Name of form element
##' @param label Label for form element (typically rendered to the left of the element)
##' @param description Description for form element (typically rendered as roll-over text)
##' @param value The default, starting value, for the form (default: "")
##' @param type Type of form element. This can be "text", "textarea", "checkbox", "password" or "file"
##' @param advanced Integer. 0 means the option is not advanced, and increasing levels indicate the option
##' is for more advanced users. Advanced > 0 should be hidden under default mode.
##' @param show.if A list of two elements: \code{$name}, giving the name of some other param in the set
##' and \code{$values}, a character vector of values. The parameter under construction should only be shown if
##' the named parameter takes on any of the values in the \code{$values} vector. Default (NULL) is to always
##' show the parameter under construction.
##' @param display.callback NULL, to follow \code{show.if} logic in deciding when/if
##' to display the element, or a list with the two elements \code{$uri} and \code{$dependent},
##' which follow the same format as the corresponding arguments to \code{combobox.param} and provide
##' a templated uri and a mapping from template variables to form parameters. The service should
##' return JSON true if the widget is to be shown and false if not. It is allowed to
##' have both a \code{display.callback} and a \code{show.if}---both conditions must be
##' met in order to display the element.
##' @param size A word giving the size of the element. The interpretation of this size is up to the front-end.
##' Must from a defined set of words, which you can see by calling \code{known.param.sizes()} (currently
##' no way to change this). Default: medium
##' @param required Logical. Is this a required param? Default: TRUE. If set, then the front-end will
##' the front-end user is required to set this parameter before submitting the request. The meaning of
##' "set this parameter" is not entirely clear.
##' @param persistent Character or NULL. If non-NULL then it is passed to the front-end. It names a
##' variable in persistent storage that should be used to initialize the value of the parameter.
##' The front end will provide some mechanism to change the persistent value, but until the user does
##' so the param will be initialized from the value in the persistent space.
##' @param persistent.dependencies A character vector or NULL (default) specifying the names
##' of other parameters on
##' which this one "depends". It is an error to include the parameter itself (\code{name}).
##' It is an error to provide this when \code{persistent} is NULL.
##' When \code{persistent} is non-NULL, providing \code{persistent.dependencies} makes this parameter
##' not just "persistent" but "conditionally persistent", which
##' is to say that the persistent value for this parameter is actually a hash lookup based on the
##' the other parameters specified in this vector. A typical example would be a pheno fields parameter
##' which is dependent on the study parameter. The names are taken from the Page namespace, which
##' means that a parameter's \code{$name} is used when this differs from its \code{$persistent}
##' slot.
##' @param transformer A function with signature \code{function(value, self)} or \code{function(value)} which accepts
##' as first argument the JSON-decoded value of the parameter returned from the front end and then performs
##' some sort of transformation. The return value of the function will be ultimately
##' passed to AnalysisPage handler. For example, for a field which is a simple text widget
##' but which is supposed to be numeric you might use \code{transformer = as.numeric}. But you could
##' also implement more complicated logic here. The reason to put the logic here instead of in the handler
##' is that it makes it easier to re-use the widget in multiple handlers. If you have a complex (nested) parameter
##' then the nested elements' transformations, if any, are applied first, then the parental transformation is applied.
##' Or, NULL (default) to not
##' do any transformation beyond the JSON decoding.
##' @return An AnalysisPageParam. This is just a list with class name slapped on.
##' @author Brad Friedman
##' @examples
##'   x <- simple.param("xmin", label="X-min", description="Minimum x value", type="text")
##'   # Please see the "Persistent Parameters" and "Conditionally Persistent Parameters"
##'   # sections of the Interactive Apps vignette for demonstrations of these functionalities
##' @export
simple.param <- function(name,
                         label = name,
                         description = label,
                         value = "",
                         type = "text",
                         advanced = 0,
                         show.if = NULL,
                         display.callback = NULL,
                         size = "medium",
                         required = TRUE,
                         persistent = NULL,
                         persistent.dependencies = NULL,
                         transformer = NULL)  {

  .validate.known.param.size(size)
  checkStringArg <- function(arg)  {
    argname <- deparse(substitute(arg))
    .validate.string(arg,
                     paste0("simple.param() '", argname, "' argument: "))
  }
  checkStringArg(name)
  checkStringArg(label)
  checkStringArg(description)
  checkStringArg(type)
  
  if(!is.null(persistent))
    checkStringArg(persistent)
  
  if(!is.null(persistent.dependencies))  {
    if(is.null(persistent))
      stop("persistent.dependencies was provided but not persistent")
    if(name %in% persistent.dependencies)
      stop("The parameter itself ('", name, "') was included in the persistent dependencies.")
  }

  if(!is.null(transformer))  {
    is.function(transformer) || stop("transformer is not NULL or a function: ",
                                     paste(collapse = " ", is(transformer)))
    ## The reason the next line is not just
    ##   names(formals(transformer))
    ## is that if transformer is a primitive function (such as as.numeric)
    ## then formals(transformer) just returns NULL. args(transformer)
    ## makes a sort of dummy object which is itself a non-primitive function
    argNames <- names(formals(args(transformer)))
    length(argNames) %in% 1:2 || stop("transformer must have 1 or 2 arguments, but it has 0 or more than 2: ",
                                      paste(collapse = " ", argNames))
  }
    

  param <- list(name=name,
                label=label,
                description=description,
                value=value,
                type=type,
                advanced = advanced,
                show.if=show.if,
                size=size,
                required=required)

  ## This has no effect if transformer is NULL
  param$transformer <- transformer
  ## Note: The logic of the transformation is implemented by the .transform.param() function which is
  ## called by execute.handler.
  
  if(!is.null(persistent))  {
    param$persistent <- persistent
    if(!is.null(persistent.dependencies))
      param$persistent_dependencies<- persistent.dependencies
  }
  
  if(!is.null(display.callback))  param$display.callback <- display.callback
  
  class(param) <- "AnalysisPageParam"
  return(param)
}



## ap is an AnalysisPageParam or NULL, and value is the value to transform.
.transform.param.value <- function(value, ap)  {
  is.null(ap) && return(value)
  
  ## this allows a type to define a default transformer. The main reason
  ## for this is to be able to transform the children first, but to make
  ## it possible for different types to define "children" differently. In
  ## particular array and compound types need to do this.
  ## Note that this gets called even if `ap` itself does not have a transformer.
  transform.type.function.name <- paste0(".transform.param.value.", ap$type)
  if(exists(transform.type.function.name, mode="function"))  {
    transformer.type <- get(transform.type.function.name, mode="function")
    value <- transformer.type(value, ap)
  }


  transformer <- ap$transformer
  is.null(transformer) && return(value)

  ## See note in simple.param about the syntax on the next line
  if(length(formals(args(transformer))) == 1)  {
    value <- transformer(value)
  }  else  {
    ## otherwise it takes 2 args, the second one being this AnalysisPage object
    value <- transformer(value, ap)
  }

  return(value)
}




## validate the show.if value---either NULL or a length 2 list, as described in the doc for simple.param
.validate.show.if <- function(show.if)  {
  is.null(show.if) && return()
  is.list(show.if) || stop("show.if is non-NULL and not a list")
  identical(sort(names(show.if)),
            c("name", "values"))  || stop("names(show.if) is not $name, $values: ", paste(collapse=" ", names(show.if)))
}


## validate the show.if value---either NULL or a length 2 or 3 list, as described in the doc for simple.param
.validate.display.callback <- function(display.callback)  {
  is.null(display.callback) && return()
  is.list(display.callback) || stop("display.callback is non-NULL and not a list")
  invalid.names <- setdiff(names(display.callback), c("dependent", "uri", "delay.ms"))
  length(invalid.names) == 0 || stop("Invalid name(s) in display.callback: ", paste(collapse=" ", invalid.names))
  missing.names <- setdiff(c("dependent", "uri"), names(display.callback))
  length(missing.names) == 0 || stop("Missing required name(s) from display.callback: ", paste(collapse = " ", missing.names))

  ## check that all of the templated parameters are represented in the $dependent
  ## mapping
  .validate.dependent.params(uri = display.callback$uri,
                             dependent = display.callback$dependent)
}

                              
## check that the arg is an AnalysisPageParam and has the required elements. If .validate.param.{{$type}} is a function then call it
## for type-specific checking. parent.parnmaes is passed through. This allows checking for
## availability of dependent parameters from the parent, such as for show.if, display.callback and comboboxes.
.validate.param <- function(param, parent.parnames = character())  {
  is(param, "AnalysisPageParam") || stop("param is not an AnalysisPageParam: ", paste(collapse=" ", is(param)))
  missing.elements <- setdiff(c("name","label","description","value","type","advanced","show.if","size"), names(param))
  length(missing.elements) == 0 || stop("Missing required entries in AnalysisPageParam: ", paste(collapse=" ", missing.elements))

  .validate.show.if(param$show.if)
  .validate.display.callback(param$display.callback)
  .validate.known.param.size(param$size)

  validate.type.function.name <- paste(sep="", ".validate.param.", param$type)
  if(exists(validate.type.function.name, mode="function"))  {
    validator <- get(validate.type.function.name, mode="function")
    validator(param, parent.parnames = parent.parnames)
  }
  
  return()
}


##' Build a file upload AnalysisPageParam
##'
##' Build a file upload \code{AnalysisPageParam}. This is rendered as a file upload element, to be uploaded along with the submission.
##' (Currently there is no server-side mechanism for storing uploaded files, so asynchronous upload is not possible.)
##' The description field should describe what type of file is expected.
##'
##' On the server side
##' your handler will get a list with "name", "tmp_name"
##' elements giving the filename, and path to a local file (usually in /tmp)
##' @title file.param
##' @param ... Pass through to \code{\link{simple.param}}, including at least "name" but not including "type".
##' @param template.uri URI, possibly with :-prefixed parameter names. For example "/get?x=:x;y=:y" has parameters "x" and "y". (See \code{dependent.params} next).
##' (Note: this follows the way of doing it in combobox.param). This is optional. If provided, then the front-end can use this callback to allow the user to
##' download a template. This is a template in two senses: the URI itself may be a template whose parameter values should be filled in, and the return value
##' of the request is an excel file which is a template for the user to fill in.
##' @param dependent.params A character vector whose names are parameters from the uri, and whose values are the names of other form elements. Error to provide
##' this without providing template.uri
##' @return An \code{AnalysisPageParam}
##' @author Brad Friedman
##' @examples
##'   cov.param <- file.param("cov", label="Covariate Data", description="A two-column Excel file, first column being the sample ID (SAMID) and second being covariate data (with the name of the variable in the header)")
##' @export
file.param <- function(..., template.uri=NULL, dependent.params=NULL)  {
  param <- simple.param(..., type="file")
  if(is.null(template.uri) && !is.null(dependent.params))
    stop("You supplied dependent.params without supplying template.uri")
  param$template.uri <- template.uri
  param$dependent <- dependent.params
  return(param)
}


## This function takes the uri, figures out which parameters are templated there
## (by matching /:(\w+)\b/), and checks that each appears in `dependent`, which
## gives the mapping from templated parameter names to form elements.
.validate.dependent.params <- function(uri, dependent, n.param=NULL)  {
  param.pos <- gregexpr(":\\w+\\b", uri)[[1]]
  uri.params <- if(param.pos[1] == -1)  {
    character(0)
  }  else  {
    match.length <- attr(param.pos, "match.length")
    substring(uri, param.pos+1, param.pos + match.length-1)
  }
  ## The as.character() casts NULL to character, which is important if there
  ## are no params
  reqd.pars <- as.character(c(names(dependent), n.param))
  ## Note that it is possible that uri.params will contain things
  ## not in reqd.pars. That is fine---they will not be template-substituted.
  ## This can happen (a lot) if the URI contains JSON-encoded values.
  missing.pars <- setdiff(reqd.pars, uri.params)
  length(missing.pars) == 0 || stop("Dependent parameters [",
          paste(collapse = " ", missing.pars), "] not templated in URI: ",
          uri)
}


.validate.param.file <- function(param, parent.parnames = character())  {
  if(!is.null(param$template.uri))
    .validate.dependent.params(uri=param$template.uri,
                               dependent=param$dependent)
}





##' Build a select AnalysisPageParam
##'
##' Build a select AnalysisPageParam. This is probably rendered either as a dropdown or radio group. It is a selection from a fixed
##' list of possible values. The list is known before page load time
##' @title select.param
##' @param ... Passed through to \code{\link{simple.param}}. This includes at least "name", optionally "label" and "description" 
##'t not "type".
##' @param value Default value. If not specified then the first entry in choices is taken to be the default.
##' @param choices A character vector giving the choices to display. If named, then the values are used for display and names are used
##' for the actual form values. If unnamed, then the values are used for both display and names.
##' @param allow.multiple If TRUE then render as checkbox group and allow multiple selections. (The function
##' will be provided a vector of all selected values.) If \code{allow.multiple} is TRUE then \code{style} must be "dropdown".
##' Default: FALSE
##' @param style Either "dropdown" (default), to render as dropdown list, or "radio", to render as radio group.
##' If \code{allow.multiple} is TRUE then \code{style} must be "dropdown".
##' @return An AnalysisPageParam
##' @author Brad Friedman
##' @examples
##'   color <- select.param("color", label="Color", description="Color of your house", choices=c("red","green","mauve","tope"))
##' @export
select.param <- function(..., value, choices,
                         allow.multiple = FALSE,
                         style = "dropdown")  {
  if(is.null(names(choices)))  names(choices) <- choices
  
  if(missing(value))  {
    if(length(choices) > 0)  {
      value <- names(choices)[1]
    }  else  {
      value <- ""
    }
  }

  stopifnot(length(choices) == 0 || value %in% names(choices))
  
  param <- simple.param(..., value = value, type="select")
  param$choices <- choices
  style %in% c("dropdown", "radio") || stop("style must be 'dropdown' or 'radio', you gave '", style, "'")
  param$style <- style
  param$allow_multiple <- allow.multiple

  return(param)
}

.validate.param.select <- function(param, parent.parnames = character())  {
  is.character(param$choices) || stop("param$choices is not character: ", paste(collapse=" ", is(param$choices)))
  param$style %in% c("dropdown", "radio") || stop("style must be 'dropdown' or 'radio', you gave '", param$style, "'")
  is.logical(param$allow_multiple) || stop("allow_multiple must be a logical: ", paste(collape= " ", is(param$allow_multiple)))
  length(param$allow_multiple == 1) || stop("length(allow_multiple) is not 1: ", length(param$allow_multiple))
  if(param$allow_multiple && param$style != "dropdown")
    stop("style must be 'dropdown' with allow_multiple=TRUE")
}





##' Build a slider AnalysisPageParam
##'
##' Build a slider AnalysisPageParam. This is a numeric variable. It has a minimum value, a maximum value, and a step size
##' @title slider.param
##' @param ... Passed through to \code{\link{simple.param}}. This includes at least "name", optionally "label" and "description" 
##' but not "type".
##' @param min Minimum value (number)
##' @param max Maximum value (number)
##' @param step Size of one step (must be <= max-max)
##' @param value Default value. If not specified then the minimum is taken to be the default
##' @return An AnalysisPageParam
##' @author Brad Friedman
##' @examples
##'   slider <- slider.param("children", label="No. Children", description="Number of Children", min = 0, max = 10, step = 0.5)
##' @export
slider.param <- function(..., min, max, step, value = min)  {
  min < max || stop("min (", min, ") must be >= max (", max, ")")
  step <= (max- min) || stop("step (", step, ") is greater than max-min (", max-min, ")")
  step > 0 || stop("step (", step, ") is not > 0")

  param <- simple.param(..., value = value, type="slider")
  param$min <- min
  param$max <- max
  param$step <- step

  return(param)
}


.validate.param.slider <- function(param, parent.parnames = character())  {
  is.numeric(param$min) || stop("param$min is not numeric: ", paste(collapse=" ", is(param$min)))
  is.numeric(param$max) || stop("param$max is not numeric: ", paste(collapse=" ", is(param$max)))
  is.numeric(param$step) || stop("param$step is not numeric: ", paste(collapse=" ", is(param$step)))

  for(pname in c("min", "max", "step"))
    length(param[[pname]]) == 1 || stop("param$", pname, " should have length one, but it has length ", length(param[[pname]]))

}




##' Build a boolean AnalysisPageParam
##'
##' Build a boolean AnalysisPageParam. This is probably rendered as a checkbox. The value returned to
##' the server should be either JSON "true" (corresponding to checked) or JSON "false".
##'
##' If you do not provide a value (or if you provide value="", which is what happens in the parent
##' constructor \code{simple.param} when you don't provide a value) then the default will be FALSE.
##' @title bool.param
##' @param ... Passed through to \code{\link{simple.param}}. This includes at least "name", and optionally
##' "label", "description" and "value" (which should be either \code{TRUE} or \code{FALSE}),
##' but not "type".
##' @return An AnalysisPageParam
##' @author Brad Friedman
##' @examples
##'   show.ids <- bool.param("show_ids", label="Show IDs", description="Show sample IDs on the plot", value=TRUE)
##' @export
bool.param <- function(...)  {
  param <- simple.param(..., type="bool")

  if(param$value == "")  param$value <- FALSE
  
  return(param)
}

.validate.param.bool <- function(param, parent.parnames = character())  {
  is.logical(param$value) || stop("default value ", paste(collapse=":", param$value), " is not logical: ",
                                  paste(collapse=" ", is(param$value)))
  length(param$value) == 1 || stop("default value has length ", length(param$value), " != 1")
}









##' Build a combobox AnalysisPageParam
##'
##' Build a combobox AnalysisPageParam. This is a widget with both text and drop-down. However, the values in the drop-down
##' depend on an AJAX query whose URI is built from current form element values, possibly including the current widget (namely
##' the text typed so far). The drop-down values should be updated
##' whenever one of the depenedent elements changes.
##'
##' @title combobox.param
##' @param ... Passed through to \code{\link{simple.param}}. This includes at least "name", optionally "label", "description" and "value", but not "type".
##' @param uri URI, possibly with :-prefixed parameter names. For example "/get?x=:x;y=:y" has parameters "x" and "y". (See \code{dependent.params} next)
##' @param dependent.params A character vector whose names are parameters from the uri, and whose values are the names of other form elements.
##' @param prompt A prompt to display in disabled style before user starts typing (for self-dependent comboboxes only). Default is "Enter search term".
##' @param n.param The name of a parameter that controls the maximum number of search hits, if the URL has such a parameter. Default is NULL, which
##' means it does not have a parameter. If it does have such a parameter, then 0 means to return all hits, and otherwise a positive integer will
##' limit the number of hits returned.
##' @param allow.multiple If TRUE then render search hits as checkbox group and allow multiple selections. (The function
##' will be provided a vector of all selected values.) If the search term changes then the old values
##' are still accumulated. If other dependent parameter changes then they are reset.
##' Default: FALSE
##' @param response.type A string. "simple" means that the response will be a simple array
##' of strings. The "id-long_name-reason" type is a special type for search hits, where an array of hashes is returned, each
##' hash having "id", "long_name" and "reason" components.
##' @param delay.ms Delay, in milliseconds, that the front-end should wait after keystrokes or paste before submitting queries. Default, 0, means no delay.
##' @inheritParams simple.param
##' @param extra.persistent.dependencies Character vector specifying names of other parameters on which this one is conditionally persistent. See arg
##' \code{persistent.dependencies} in \code{\link{simple.param}}. Whatever is provided in \code{extra.persistent.dependencies}
##' is taken \emph{in addition to} with \code{unname(dependent.params)}. This is because
##' if a combobox is persistent then it must be so conditional on its dependent parameters.
##' It is an error to specify this for a non-persistent parameter.
##' @return An AnalysisPageParam
##' @author Brad Friedman
##' @examples
##'   ## Note the :query parameter is dependent on the same gene element. This makes it a type-ahead query.
##'   gene <- combobox.param(name="gene", uri="/find_gene_id/:genome/:query/", dependent.params=c(genome="genome", query="gene"), response.type="id-long_name-reason")
##' @export
combobox.param <- function(name, ..., uri, dependent.params, prompt = "Enter search term", n.param=NULL,
                           allow.multiple = FALSE,
                           response.type="simple",
                           persistent = NULL,
                           extra.persistent.dependencies = NULL,
                           delay.ms = 0)  {
  ## A dynamic list of options. The list is not known before page load time. It has to be found out by an AJAX request.
  ## This is made after substituting :-prefixed parameters in the uri according to the current values of other fields,
  ## specified in dependent.params.
  ##
  ## These dynamic lists are either populated as a dropdown---with the list updated by AJAX call whenever the value of
  ## one of the dependent parameters changes---or during typeahead, with the current value provided as one of the parameters.
  ## Its actually the same thing---the second is a special case of the first, where the same element is one of the dependnet values.

  if(!is.null(persistent))  {
    auto.detected.persistent.dependencies <- setdiff(unname(dependent.params), name)
    persistent.dependencies <- unique(c(auto.detected.persistent.dependencies,
                                        extra.persistent.dependencies))
  }  else  {
    persistent.dependencies <- NULL
  }
  param <- simple.param(name = name,
                        ..., type="combobox",
                        persistent = persistent,
                        persistent.dependencies = persistent.dependencies)
  param$uri <- uri
  param$dependent <- dependent.params
  param$response.type <- response.type
  param$prompt <- prompt
  param$allow_multiple <- allow.multiple
  param$delay.ms <- delay.ms
  ## the following weird construction forces incorporate the $n.param element into the list even when it is NULL.
  ## If you do
  ##   listvar$elem <- NULL
  ## it has no effect if $elem is not already in the list, and it actually delete the element if it is already in the
  ## list
  ## And there is further weirdness in that the c(., list())  construction will strip the param of its class!
  save.class <- class(param)
  param <- c(param, list(n.param= n.param))
  class(param) <- save.class
  
  return(param)
}

.validate.param.combobox <- function(param, parent.parnames = character())  {
  .validate.dependent.params(uri=param$uri,
                             dependent=param$dependent,
                             n.param=param$n.param)
  is.null(param$delay.ms) || (is.numeric(param$delay.ms) && param$delay.ms >= 0) || stop("delay.ms must be a non-negative number: ", param$delay.ms)
  
  is.logical(param$allow_multiple) || stop("allow_multiple must be a logical: ", paste(collape= " ", is(param$allow_multiple)))
  length(param$allow_multiple == 1) || stop("length(allow_multiple) is not 1: ", length(param$allow_multiple))
}









##' Create a Compound AnalysisPageParam
##'
##' A compound AnalysisPageParam is a single parameter that has multiple parts.
##' The parts are represented by an AnalysisPageParamSet, so could be arbitrarily
##' nested. The front end is responsible for wrapping up all of the values
##' in a JSON hash and passing in a single value.
##'
##' This can be thought of as a way of building a hash out of other parameter types.
##' @title compound.param
##' @param ... Passed through to \code{\link{simple.param}}. This includes at least "name", optionally "label" and "description".
##' but not "type" (which is set to "compound") or "value" (which is set to empty string, but ignored anyway, since each of the
##' contained parameters will have its own value).
##' @param children AnalysisPageParamSet An AnalysisPageParamSet representing all of the nested parameters
##' @return AnalysisPageParam of type "compound"
##' @author Brad Friedman
##' @examples
##'   plist <- list(simple.param(name="study"), simple.param(name="comp"), simple.param("feature.type", value="gene"))
##'   comp <- compound.param(name="comp", label="Comparison", children=param.set(plist))
##' @export
compound.param <- function(..., children)  {
  param <- simple.param(..., type="compound", value="")
  param$children <- children
  return(param)
}

.validate.param.compound <- function(param, parent.parnames = character())  {
  .validate.paramset(param$children, parent.parnames = parent.parnames)
}

.transform.param.value.compound <- function(value, ap)  {
  ## I think next line should be unreachable, but if it is reached then something
  ## is definitely wrong and the code below will not be doing what it is supposed to.
  ## Note that some values might be missing---that's OK. We
  ## only have to transform the ones that are present.
  stopifnot(names(value) %in% names(ap$children))
  valueNames <- names(value)
  value <- lapply(setNames(valueNames, valueNames),
                  function(valueName)  {
                    subValue <- value[[valueName]]
                    subParam <- ap$children[[valueName]]
                    .transform.param.value(subValue, subParam)
                  })
  return(value)
}








##' Create an Array AnalysisPageParam
##'
##' An array AnalysisPageParam is a way of having a single parameter with
##' multiple repetitions of some other (fixed) parameter type. The
##' starting length, as well as minimum and maximum allowable lengths,
##' are provided. If min != max then the front end should render some
##' widget to add/remove elements.
##'
##' By combining with \code{compound.param} a fairly complex data
##' structure can now be specified.
##' @title array.param
##' @param ... Passed through to \code{\link{simple.param}}. This includes at least "name", optionally "label" and "description".
##' but not "type" (which is set to "array") or "value" (which is set to empty string, but ignored anyway, since the
##' prototype parameter will have its own value).
##' @param prototype A single AnalysisPageParam that is the prototype for each of the elements in the array. Note
##' that while only one param is allowed, it could potentially be either a compound or another array parameter.
##' @param start The starting length of the array that should be rendered
##' @param min The minimum allowed length of the array. Buttons to remove elements should be de-activated below this level (default 0).
##' @param max The maximum allowed length of the array. Buttons to add elements should be de-activated above this level (default Inf).
##' @return AnalysisPageParam of type "array"
##' @examples
##'   one.gene <- simple.param(name="gene", label="Gene Symbol")
##'   gene.set <- array.param(name="geneset", prototype=one.gene)
##' @author Brad Friedman
##' @export
array.param <- function(..., prototype, start=1, min=0, max=Inf)  {
  stopifnot(min <= start)
  stopifnot(start <= max)
  param <- simple.param(..., type="array", value="")
  param$prototype <- prototype
  param$start <- start
  param$min <- min
  param$max <- max
  return(param)
}

.validate.param.array <- function(param, parent.parnames = character())  {
  .validate.param(param$prototype, parent.parnames = parent.parnames)
}


.transform.param.value.array <- function(value, ap)  {
  value <- lapply(value, .transform.param.value, ap$prototype)
  return(value)
}
apomatix/AnalysisPageServer documentation built on May 26, 2019, 11:36 a.m.