I need to pass a few parameters to Javascript, but for a strange reason it does not compile. I started with:
port check : String -> Cmd msg
this works fine (as taken directly from JavaScript Interop). But when I am adding another parameter
port check : Int -> String -> Cmd msg
I am getting
1| port check : Int -> String -> Cmd msg
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You are saying it should be:
Int -> String -> Platform.Cmd.Cmd msg
But you need to use the particular format described here:
http://guide.elm-lang.org/effect_managers/
I solved this simply by reducing arguments back to one
type alias Bundle = (Int, String)
port check : Bundle -> Cmd msg
but that would be cleaner if I could simply do
app.ports.check.subscribe(function(arg1, arg2) {
});
Why it doesn't compile?
ports can only have one parameter. If you need to pass multiple parameters then your only options are to pass a tuple (like you did with Bundle) or a record.
On the js side, you'll have to take one parameter but you can destructure it after with whatever var names you want
app.ports.check.subscribe(function(arg) {
var [arg1, arg2] = arg;
});
If you're using ES6, then you do have some nicer options for destructuring in the function params if you use a record like this:
app.ports.check.subscribe(function({arg1: arg1, arg2: arg2}) {
});
I noticed you don't really have to "decode" much on the Javascript side. So, you can package your single parameter up as a nice type in ELM.
Do something like this in your elm:
type alias MyObject =
{ name: String
, displayDate: String
, subTitle: String
, hashTag: String
}
port check : MyObject -> Cmd msg
Then in the javascript you can just do this:
app.ports.check.subscribe(function(myObject) {
alert( myObject.name);
alert( myObject.displayDate);
alert( myObject.subTitle);
alert( myObject.hashTag);
});
Related
I have an Elm file with some HTML content in a function called view.
Part of the HTML content is dynamically generated via a map function that extracts data from a record.
Below is the code from that file.
module PhotoGroove exposing (main)
import Html exposing (div,h1,img,text)
import Html.Attributes exposing (..)
--model
initialModel : { photos : List { url : String }, selectedPhoto : String }
initialModel =
{ photos =
[ { url = "1.jpeg" }
, { url = "2.jpeg" }
, { url = "3.jpeg" }
]
, selectedPhoto = "1.jpeg"
}
--view
urlPrefix : String
urlPrefix = "http://elm-in-action.com/"
viewThumbnail : String -> { a | url : String } -> Html.Html msg
viewThumbnail selectedPhoto thumb =
if selectedPhoto == thumb.url
then img [src (urlPrefix ++ thumb.url), class "selected"] []
else img [src (urlPrefix ++ thumb.url)] []
view : List { a | url : String } -> Html.Html msg
view model =
div [class "context"]
[h1 [] [text "Photo Groove"]
,div [id "tumbnail"] (List.map viewThumbnail model) -- error message on "model"
]
--main
main = view initialModel -- error message on "initialModel"
However, I got error messages on the words "model" and "initialModel".
Below is the error message for "model":
TYPE MISMATCH - The 2nd argument to `div` is not what I expect:
29| ,div [id "tumbnail"] (List.map viewThumbnail model)
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^#
This `map` call produces:
List #({ a | url : String } -> Html.Html msg)#
But `div` needs the 2nd argument to be:
List #(Html.Html msg)#
#Hint#: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
And for initialModel:
TYPE MISMATCH - The 1st argument to `view` is not what I expect:
35| main = view initialModel
#^^^^^^^^^^^^#
This `initialModel` value is a:
#{ photos : List { url : String }, selectedPhoto : String }#
But `view` needs the 1st argument to be:
#List { a | url : String }#
I got that I need to somehow convert the objects directly into HTML msg types, but I don't know how.
I even tried to change the type for view, but that did not work either.
How do I fix this problem?
This is a great example of Elm's type system (or a good static type system in general) helping you figure out logical errors in your code.
The problems here are all due to your view function, and in particular with faulty assumptions it makes about what is in your model.
You are trying to map a function over the model argument, which assumes model is a list - but it isn't, it's a record containing both a list of all photos and a string identifying the selected photo. That makes logical sense as a model to use in this situation, so rather than changing the model type (which would otherwise be a sensible option) I would suggest changing view so that it works with your actual model type.
Another problem, which is highlighted in the first of the 2 error messages you quote, is that viewThumbnail can't be mapped over a list of photos anyway because it actually takes 2 arguments, the first of which isn't a list. You do want to use this function to produce the list of images in view's output - but you also need to provide it with the selectedPhoto, which of course comes from the other field in your model that you've ignored so far.
So the only changes needed to your original code to get this to compile are:
change the type signature of view to actually match your model's type. (This is what the second of the two error messages is about). It should be view : { photos : List { url : String }, selectedPhoto : String } -> Html.Html msg (You probably want to make a type alias for this model type, at least if you expand this app to any much greater size.)
inside the function body,
change this
List.map viewThumbnail model
to
List.map (\photo -> viewThumbnail model.selectedPhoto photo) model.photos
Hopefully you can see what the above is doing - the \ is for an anonymous function, that takes one of the photos and calls viewThumbnail with that as second argument and model.selectedPhoto always as the first argument.
And note finally that you can simplify the above by taking advantage of Elm's built-in function currying, to just
List.map (viewThumbnail model.selectedPhoto) model.photos
which I and probably most others familiar with functional programming find much cleaner and easy to read.
I'm using HOCON to configure log messages and I'm looking for a way to substitute placeholder values dynamically.
I know that ${?PLACEHOLDER} will read an environment variable and returns an empty string when the PLACEHOLDER environment variable doesn't exist.
Example
This is an example of what I had in mind:
(I'm using config4k to load HOCON )
data class LogMessage(val message: String, val code: String)
fun getMessage(key: String, placeholderValues: Array<String> = arrayOf()): LogMessage {
val config = ConfigFactory.parseString("MY_LOG_MESSAGE {code = ABC-123456, message = My log message with dynamic value %0% and another dynamic value %1% }")
val messageObject = config.extract<LogMessage>(key)
var message = messageObject.message
placeholderValues.forEachIndexed { placeholderIndex, value ->
message = message.replace("%$placeholderIndex%", value)
}
return messageObject.copy(message = message)
}
fun main(args: Array<String>) {
println(getMessage("MY_LOG_MESSAGE", arrayOf("value 1", "value 2")))
// prints: LogMessage(message=My log message with dynamic value value 1 and another dynamic value value 2, code=ABC-123456)
}
Even though this works, it doesn't look like the best solution and I assume there already is a solution for this.
Could someone tell me if there is a built-in solution?
First things first.
HOCON is just a glorified JSON format.
config4k is just a wrapper.
All your work is being done by Typesafe Config, as you've probably noticed.
And judging by their documentation and code, they support placeholders only from withing the file, or from the environment:
This library limits itself to config files. If you want to load config
from a database or something, you would need to write some custom
code.
But for what you're doing, simple String.format() should be enough:
fun interpolate(message: String, vararg args: Any) = String.format(message, *args)
println(interpolate("My message was %s %s %s %s", "a", 1, 3.32, true))
// My message was a 1 3.32 true
Notice that you can use * to destructure your array.
Specifically, I am trying to initialize Elm with an already defined parameter. Something like:
initialModel =
{ me = window.user
, todos = window.todos
}
All I can find is how to get window dimensions using signals, but I'm on Elm 0.18 and it seems slightly outdated.
Edit: Just to be clear, the above code wouldn't work. Whatever was attached to the window object would have to be JS, so it'd have to go through a decoder.
You will need to use programWithFlags to pass initial values from javascript. The "flags" you pass from javascript should have equivalent record type if you want to use Elm's automatic type conversion:
Let's say your me is just a string, but your todos is a list of boolean flags and a label. Your Flags type could look like this:
type alias Todo =
{ done : Bool, label : String }
type alias Flags =
{ me : String, todos : List Todo }
Your init function would need to handle the flags value appropriately. Here is an example of just assigning the fields to Model fields of the same name:
type alias Model =
{ me : String, todos : List Todo }
main : Program Flags Model Msg
main =
Html.programWithFlags
{ init = init
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
init : Flags -> ( Model, Cmd Msg )
init flags =
{ me = flags.me, todos = flags.todos } ! []
Your javascript will need to be updated to pass in the flags. You do that by passing a json object as the first parameter to fullscreen or embed:
var app = Elm.Main.fullscreen({
"me": "John Doe",
"todos": [
{ done: true, label: "Do this thing" },
{ done: false, label: "And this thing" }
]
});
Here is a working example on ellie-app.com
If Elm's automatic json-to-mapping conversion isn't strong enough for your decoding, you could instead use the Json.Decode.Value type as your flag, then Json.Decode.decodeValue using your customer decoder. Here is an example on ellie-app.com of using a custom decoder.
I would like to pass a complete JSON object to a java adapter in worklight. This adapter will call multiple other remote resources to fulfill the request. I would like to pass the json structure instead of listing out all of the parameters for a number of reasons. Invoking the worklight procedure works well. I pass the following as the parameter:
{ "parm1": 1, "parm2" : "hello" }
Which the tool is fine with. When it calls my java code, I see a object type of JSObjectConverter$1 being passed. In java debug, I can see the values in the object, but I do not see any documentation on how to do this. If memory serves me, the $1 says that it is an anonymous inner class that is being passed. Is there a better way to pass a json object/structure in adapters?
Lets assume you have this in adapter code
function test(){
var jsonObject = { "param1": 1, "param2" : "hello" };
var param2value = com.mycode.MyClass.parseJsonObject(jsonObject);
return {
result: param2value
};
}
Doesn't really matter where you're getting jsonObject from, it may come as a param from client. Worklight uses Rhino JS engine, therefore com.mycode.MyClass.parseJsonObject() function will get jsonObject as a org.mozilla.javascript.NativeObject. You can easily get obj properties like this
package com.mycode;
import org.mozilla.javascript.NativeObject;
public class MyClass {
public static String parseJsonObject(NativeObject obj){
String param2 = (String) NativeObject.getProperty(obj, "param2");
return param2;
}
}
To better explain what I'm doing here, I wanted to be able to pass a javascript object into an adapter and have it return an updated javascript object. Looks like there are two ways. The first it what I answered above a few days ago with serializing and unserializing the javascript object. The other is using the ScriptableObject class. What I wanted in the end was to use the adapter framework as described to pass in the javascript object. In doing so, this is what the Adapter JS-impl code looks like:
function add2(a) {
return {
result: com.ibm.us.roberso.Calculator.add2(a)
};
The javascript code in the client application calling the above adapter. Note that I have a function to test passing the javascript object as a parameter to the adapter framework. See the invocationData.parameters below:
function runAdapterCode2() {
// x+y=z
var jsonObject = { "x": 1, "y" : 2, "z" : "?" };
var invocationData = {
adapter : "CalculatorAdapter",
procedure : 'add2',
parameters : [jsonObject]
};
var options = {
onSuccess : success2,
onFailure : failure,
invocationContext : { 'action' : 'add2 test' }
};
WL.Client.invokeProcedure(invocationData, options);
}
In runAdapterCode2(), the javascript object is passed as you would pass any parameter into the adapter. When worklight tries to execute the java method it will look for a method signature of either taking an Object or ScriptableObject (not a NativeObject). I used the java reflection api to verify the class and hierarchy being passed in. Using the static methods on ScriptableObject you can query and modify the value in the object. At the end of the method, you can have it return a Scriptable object. Doing this will give you a javascript object back in the invocationResults.result field. Below is the java code supporting this. Please note that a good chunk of the code is there as part of the investigation on what object type is really being passed. At the bottom of the method are the few lines really needed to work with the javascript object.
#SuppressWarnings({ "unused", "rawtypes" })
public static ScriptableObject add2(ScriptableObject obj) {
// code to determine object class being passed in and its heirarchy
String result = "";
Class objClass = obj.getClass();
result = "objClass = " + objClass.getName() + "\r\n";
result += "implements=";
Class[] interfaces = objClass.getInterfaces();
for (Class classInterface : interfaces) {
result += " " + classInterface.getName() ;
}
result += "\r\nsuperclasses=";
Class superClass = objClass.getSuperclass();
while(superClass != null) {
result += " " + superClass.getName();
superClass = superClass.getSuperclass();
}
// actual code working with the javascript object
String a = (String) ScriptableObject.getProperty((ScriptableObject)obj, "z");
ScriptableObject.putProperty((ScriptableObject)obj, "z", new Long(3));
return obj;
}
Note that for javascript object, a numeric value is a Long and not int. Strings are still Strings.
Summary
There are two ways to pass in a javascript object that I've found so far.
Convert to a string in javascript, pass string to java, and have it reconstitute into a JSONObject.
Pass the javascript object and use the ScriptableObject classes to manipulate on the java side.
Is there a way to rename the query string parameter that holds the name of callback function? Say, I've got a legacy app which sources I can't access, I want it to be switched to ServiceStack, but the app uses "function" query string parameter, while SS expects "callback".
You can do it with a response filter, inside AppHost.Configure():
ResponseFilters.Add((req, res, dto) =>
{
var func = req.QueryString.Get("function");
if (!func.isNullOrEmpty())
{
res.AddHeader("Content-Type", ContentType.Html);
res.Write("<script type='text/javascript'>{0}({1});</script>"
.FormatWith(func, dto.ToJson()));
res.Close();
}
});