Fable: js object : Best way to create and object with a js function inside?

Created on 29 Mar 2018  路  4Comments  路  Source: fable-compiler/Fable

Hi, working on PouchDB, I would like to pass the following object:

{
  _id: '_design/index',
  views: {
    index: {
      map: function mapFun(doc) {
        emit(doc);
      }.toString()
    }
  }
}

My question is simple: what's the best way to create such an object using Fable? More precisely, how do I create a JS function and pass it to the map field?

Thanks!

Most helpful comment

@whitetigle I am a bit surprise by the named function and the toString() at the end of it.

From our previous conversation, I suspect pouchDb will try to deserialize the "object" into a json representation and re-build it on the other. (it's probably the reason of the named function or toString.

You can find here a live version of the following code. I didn't try to add neither of the name and toString because I am not sure this is intended or no in your code. If you need them, we probably need to explore a bit more Fable capabilities or use macro to generate the right code.

open Fable.Core
open Fable.Core.JsInterop

// Create an jObject manually
let data1 =
    createObj [
        "_id" ==> "_design/index"
        "views" ==> 
            createObj [
                "index" ==> 
                    createObj [
                        "map" ==> fun doc ->
                            printfn "%A" doc
                    ]
            ]
    ]

// Use an interface
type IRequest =
    abstract _id: string with get, set
    abstract views: IViews with get, set

and IViews =
    abstract index: IIndex with get, set

and IIndex =
    abstract map: (obj -> unit) with get, set


let request = createEmpty<IRequest>
let views = createEmpty<IViews>
let index = createEmpty<IIndex>

index.map <- fun doc -> printfn "%A" doc
views.index <- index
request._id <- "_design/index"
request.views <- views

// Use interface with jsOptions
let request2 = jsOptions<IRequest>(fun r ->
    r._id <- "_design/index"
    r.views <- jsOptions<IViews>(fun v ->
        v.index <- jsOptions<IIndex>(fun i ->
            i.map <- fun doc -> printfn "%A" doc
        )
    )
)

// Use keyValueList
[<Pojo>]
type RRequest =
    { _id : string
      views : RViews }

and [<Pojo>] RViews =
    { index : RIndex }

and [<Pojo>] RIndex =
    { map: obj -> unit }

let request3 =
    { _id = "_design/index"
      views =
        { index = 
            { map = fun doc -> printfn "%A" doc } } }


type KRequest =
    | [<CompiledName("_id")>] Id of string
    static member Views (props: KViews list): KRequest = unbox("views", keyValueList CaseRules.LowerFirst props)

and KViews =
    static member Views (props : KIndex list) : KViews = unbox("index", keyValueList CaseRules.LowerFirst props)

and KIndex =
    | Map of (obj -> unit)


let requestK =
    [ KRequest.Id "_design/index"
      KRequest.Views 
        [ KViews.Views 
            [ KIndex.Map (fun doc -> printfn "%A" doc )]
        ]
    ]
    |> keyValueList CaseRules.LowerFirst


Fable.Import.JS.console.log requestK

All 4 comments

Probably not the best way, but the fastest if you have many object literals like these and you don't feel like giving everything a type, just use createObj:

open Fable.Core
open Fable.Core.JsInterop

// your emit logic
let emit (x: 'a) = int x

let record = 
    createObj [ 
      "_id" ==> "_design/index"
      "view" ==>
        createObj [
           "index" ==>
              createObj [
                  "map" ==> unbox (fun doc -> emit doc) 
              ] 
        ]
    ]

Translates to:

export function emit(x) {
  return x | 0;
}

export const record = {
  _id: "_design/index",
  view: {
    index: {
      map: function (doc) {
        return emit(doc);
      }
    }
  }
};

@whitetigle I am a bit surprise by the named function and the toString() at the end of it.

From our previous conversation, I suspect pouchDb will try to deserialize the "object" into a json representation and re-build it on the other. (it's probably the reason of the named function or toString.

You can find here a live version of the following code. I didn't try to add neither of the name and toString because I am not sure this is intended or no in your code. If you need them, we probably need to explore a bit more Fable capabilities or use macro to generate the right code.

open Fable.Core
open Fable.Core.JsInterop

// Create an jObject manually
let data1 =
    createObj [
        "_id" ==> "_design/index"
        "views" ==> 
            createObj [
                "index" ==> 
                    createObj [
                        "map" ==> fun doc ->
                            printfn "%A" doc
                    ]
            ]
    ]

// Use an interface
type IRequest =
    abstract _id: string with get, set
    abstract views: IViews with get, set

and IViews =
    abstract index: IIndex with get, set

and IIndex =
    abstract map: (obj -> unit) with get, set


let request = createEmpty<IRequest>
let views = createEmpty<IViews>
let index = createEmpty<IIndex>

index.map <- fun doc -> printfn "%A" doc
views.index <- index
request._id <- "_design/index"
request.views <- views

// Use interface with jsOptions
let request2 = jsOptions<IRequest>(fun r ->
    r._id <- "_design/index"
    r.views <- jsOptions<IViews>(fun v ->
        v.index <- jsOptions<IIndex>(fun i ->
            i.map <- fun doc -> printfn "%A" doc
        )
    )
)

// Use keyValueList
[<Pojo>]
type RRequest =
    { _id : string
      views : RViews }

and [<Pojo>] RViews =
    { index : RIndex }

and [<Pojo>] RIndex =
    { map: obj -> unit }

let request3 =
    { _id = "_design/index"
      views =
        { index = 
            { map = fun doc -> printfn "%A" doc } } }


type KRequest =
    | [<CompiledName("_id")>] Id of string
    static member Views (props: KViews list): KRequest = unbox("views", keyValueList CaseRules.LowerFirst props)

and KViews =
    static member Views (props : KIndex list) : KViews = unbox("index", keyValueList CaseRules.LowerFirst props)

and KIndex =
    | Map of (obj -> unit)


let requestK =
    [ KRequest.Id "_design/index"
      KRequest.Views 
        [ KViews.Views 
            [ KIndex.Map (fun doc -> printfn "%A" doc )]
        ]
    ]
    |> keyValueList CaseRules.LowerFirst


Fable.Import.JS.console.log requestK

Thanks guys!
This morning I tried three methods:

  • use js imports (=code in js),
  • use createObj
  • use interfaces.

Since this kind of object is somehow dynamic (id label must match the field name in the field views) I ended up using createObj.

I feel Interfaces are way to complex just like KeyValueList but then it's because the object structure has nested fields so it's understandable.

Anyway, thanks for your explanations and samples.
I think this information should be added somewhere. Maybe in as an update to your awesome jsInterop tutorial @Zaid-Ajaj?

Closing this :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alfonsogarciacaro picture alfonsogarciacaro  路  3Comments

nozzlegear picture nozzlegear  路  3Comments

MangelMaxime picture MangelMaxime  路  3Comments

forki picture forki  路  3Comments

alfonsogarciacaro picture alfonsogarciacaro  路  3Comments