This module allows to invoke JavaScript functions using the Elm's Task abstraction, which is convenient for chaining multiple API calls without introducing the complexity in the model of an Elm application.
Before TypePort can be used in Elm, it must be set up on JavaScript side. Refer to the README for comprehensive instructions.
String
Alias for String
type representing a name of a JavaScript function.
Valid function names only contain alphanumeric characters.
call : { function : FunctionName, valueDecoder : Json.Decode.Decoder value, argsEncoder : args -> Json.Encode.Value } -> args -> Task value
Creates a Task encapsulating an asyncronous invocation of a particular JavaScript function.
This function will usually be wrapped into a more specific one, which will partially apply it
providing the encoder and the decoders but curry the last parameter, so that it could invoked where necessary
as a args -> Task
function.
Because interop calls can fail, produced task would likely need to be piped into a Task.attempt
or handled further using Task.onError
.
Here is a simple example that creates a Cmd
invoking a registered JavaScript function called ping
and produces a message GotPong
with a Result
, containing either an Ok
variant with a string (determined by the first decoder argument),
or an Err
, containing a TaskPort.Error
describing what went wrong.
type Msg = GotWidgetName (TaskPort.Result String)
TaskPort.call
{ function = "getWidgetNameByIndex"
, valueDecoder = Json.Decode.string
, argsEncoder = Json.Encode.int
}
0
|> Task.attempt GotWidgetName
The Task
abstraction allows to effectively compose chains of tasks without creating many intermediate variants in the Msg type, and
designing the model to deal with partially completed call chain. The following example shows how this might be used
when working with a hypothetical 'chatty' JavaScript API, requiring to call getWidgetsCount
function to obtain a number
of widgets, and then call getWidgetName
with each widget's index to obtain its name.
type Msg = GotWidgets (Result (List String))
getWidgetsCount : TaskPort.Task Int
getWidgetsCount = TaskPort.callNoArgs
{ function = "getWidgetsCount"
, valueDecoder = Json.Decode.int
}
getWidgetNameByIndex : Int -> TaskPort.Task String
getWidgetNameByIndex = TaskPort.call
{ function = "getWidgetNameByIndex"
, valueDecoder = Json.Decode.string
, argsEncoder = Json.Encode.int
} -- notice currying to return a function taking Int and producing a Task
getWidgetsCount
|> Task.andThen
(\count ->
List.range 0 (count - 1)
|> List.map getWidgetNameByIndex
|> Task.sequence
)
|> Task.attempt GotWidgets
The resulting task has type TaskPort.Task (List String)
, which could be attempted as a single command,
which, if successful, provides a handy List String
with all widget names.
callNoArgs : { function : FunctionName, valueDecoder : Json.Decode.Decoder value } -> Task value
Special version of the call
that reduces amount of boilerplate code required when calling JavaScript functions
that don't take any parameters.
type Msg = GotWidgetsCount (TaskPort.Result Int)
TaskPort.callNoArgs
{ function = "getWidgetsCount"
, valueDecoder = Json.Decode.int
}
|> Task.attempt GotWidgetsCount
ignoreValue : Json.Decode.Decoder ()
JSON decoder that can be used with as a valueDecoder
parameter when calling JavaScript functions
that are not expected to return a value, or where the return value can be safely ignored.
A structured error describing exactly how the interop call failed. You can use this to determine the best way to react to and recover from the problem.
JSError
variant is for errors explicitly sent from the JavaScript side. The error information
will be specific to the interop use case, and it should be reconsituted from a JSON payload.
InteropError
variant is for the failures of the interop mechanism itself.
Result Error value
Convenience alias for a Result
obtained from passing a Task
created by one of
the variants of the TaskPort.call
function to Task.attempt'. Application code may be simplified,
because TaskPort always uses
TaskPort.Errorfor
Result.Err`.
type Msg = GotResult TaskPort.Result String
Task.attempt GotResult TaskPort.call { {- ... call details ... -} } args
Writing TaskPort.Result value
is equivalent to writing Result TaskPort.Error value
.
Task Error value
Convenience alias for a Task
created by one of the variants of the TaskPort.call
function.
Application code may be simplified, because TaskPort always uses TaskPort.Error
for the error parameter of the Tasks it creates.
callJSFunction : String -> TaskPort.Task String
callJSFunction arg = TaskPort.call { {- ... call details ... -} } arg
Writing TaskPort.Task value
is equivalent to writing Task TaskPort.Error value
.
Generic type representing all possibilities that could be returned from an interop call.
JavaScript is very lenient regarding its errors. Any value could be thrown, and, if the JS code
is asynchronous, the Promise
can reject with any value. TaskPort always attempts to decode erroneous
results returned from iterop calls using ErrorObject
variant followed by JSErrorRecord
structure, which
contains standard fields for JavaScript Error
object, but if that isn't possible, it resorts to
ErrorValue
variant followed by the JSON value as-is.
In most cases you would pass values of this type to errorToString
to create
a useful diagnostic information, but you might also have a need to handle certain types
of errors in a particular way. To make that easier, ErrorObject
variant lifts up the error
name to aid pattern-match for error types. You may do something like this:
case error of
JSError (ErrorObject "VerySpecificError" _) -> -- handle a particular subtype of Error thrown by the JS code
_ -> -- respond to the error in a generic way, e.g show a diagnostic message
{ name : String
, message : String
, stackLines : List String
, cause : Maybe JSError
}
Structure describing an object conforming to JavaScript standard for the Error
object.
Unless you need to handle very specific failure condition in a particular way, you are unlikely
to use this type directly.
The structure contains the following fields:
name
represents the type of the Error
object, e.g. ReferenceError
message
is a free-form and potentially empty string typically passed as a parameter to the error constructor
stackLines
is a platform-specific stack trace for the error
cause
is an optional nested error object, which is first attempted to be decoded as a JSErrorRecord
, but
falls back to JSError.ErrorValue
if that's not possible.
Subcategory of errors indicating a failure of the interop mechanism itself.
These errors are generally not receoverable, but you can use them to allow the application to fail gracefully,
or at least provide useful context for debugging, for which you can use helper function interopErrorToString
.
Interop calls can fail for various reasons:
NotInstalled
: JavaScript companion code responsible for TaskPort operations is missing or not working correctly,
which means that no further interop calls can succeed.
NotFound
: TaskPort was unable to find a registered function name, which means that no further calls to that function can succeed.
String value will contain the function name.
NotCompatible
: JavaScript and Elm code are not compatible. String value will contain the function name.
CannotDecodeValue
: value returned by the JavaScript function cannot be decoded with a given JSON decoder.
String value will contain the returned value verbatim, and Json.Decode.Error
will contain the error details.
* RuntimeError
: some other unexpected failure of the interop mechanism. String value will contain further details of the error.
interopErrorToString : InteropError -> String
In most cases instances of InteropError
indicate a catastrophic failure in the
application environment and thus cannot be recovered from. This function allows
Elm application to fail gracefully by displaying an error message to the user,
that would help application developer to debug the issue.
It produces multiple lines of output, so you may want to peek at it with something like this:
import Html
errorToHtml : TaskPort.Error -> Html.Html msg
errorToHtml error =
Html.pre [] [ Html.text (TaskPort.interopErrorToString error) ]
errorToString : Error -> String
Generates a human-readable and hopefully helpful string with diagnostic information describing an error. It produces multiple lines of output, so you may want to peek at it with something like this:
import Html
errorToHtml : TaskPort.JSError -> Html.Html msg
errorToHtml error =
Html.pre [] [ Html.text (TaskPort.jsErrorToString error) ]
Make sure you read section on package development in the README.
Represents the name of a function that may optionally be qualified with a versioned namespace.
String
Alias for String
type representing a namespace for JavaScript interop functions.
Namespaces are typically used by Elm package developers, and passed as a paramter to QualifiedName
.
Valid namespace string would match the following regular expression: `/^[\w-]+\/[\w-]+$/.
The following are valid namespaces: elm/core
, lobanov/elm-taskport
, rtfeldman/elm-iso8601-date-strings
.
String
Alias for String
type representing a version of a namespace for JavaScript interop functions.
Namespaces are typically used by Elm package developers, and passed as a parameter to QualifiedName
.
TaskPort does not enforce any versioning scheme and allows any combination of alphanumeric characters, dots, and dashes.
Most likely, Elm package developers will use Elm package version.
noNamespace : FunctionName -> QualifiedName
Constructs a QualifiedName
for a function in the default namespace.
It's better to use non-namespace-aware call
or callNoArgs
function, but
it's provided for completeness.
inNamespace : Namespace -> Version -> FunctionName -> QualifiedName
Constructs a QualifiedName
for a function in a particular versioned namespace.
"functionName" |> inNamespace "author/package" "version" -- infix notation reads better...
inNamespace "author/package" "version" "functionName" -- ... but this also works
callNS : { function : QualifiedName, valueDecoder : Json.Decode.Decoder value, argsEncoder : args -> Json.Encode.Value } -> args -> Task value
Creates a Task encapsulating an asyncronous invocation of a particular JavaScript function.
It behaves similarly to call
, but this function is namespace-aware and is intended to be used
by Elm package developers, who want to use TaskPort's function namespaces feature to eliminate a possibility
of name clashes of their JavaScript functions with other packages that may also be using taskports.
Unlike call
, this function uses a record to specify the details of the interop call, which leads to more readable code.
TaskPort.callNS
{ function = TaskPort.WithNS "elm-package/namespace" "1.0.0" "setWidgetName"
, valueDecoder = TaskPort.ignoreValue -- expecting no return value
, argsEncoder = Json.Encoder.string
}
"new name"
|> Task.attempt WidgetNameUpdated
callNoArgsNS : { function : QualifiedName, valueDecoder : Json.Decode.Decoder value } -> Task value
Creates a Task encapsulating an asyncronous invocation of a particular JavaScript function without parameters.
It behaves similarly to callNoArgs
, but this function is namespace-aware and is intended to be used
by Elm package developers, who want to use TaskPort's function namespaces feature to eliminate a possibility
of name clashes of their JavaScript functions with other packages that may also be using taskports.
Unlike callNoArgs
, this function uses a record to specify the details of the interop call, which leads to more readable code.
TaskPort.callNoArgsNS
{ function = TaskPort.WithNS "elm-package/namespace" "1.0.0" "getWidgetName"
, valueDecoder = Json.Decoder.string -- expecting a string
}
|> Task.attempt GotWidgetName