diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 8e01b65..b745001 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -14,17 +14,17 @@ "fable" ] }, - "fantomas-tool": { - "version": "4.4.0", - "commands": [ - "fantomas" - ] - }, "femto": { "version": "0.16.0", "commands": [ "femto" ] + }, + "fantomas": { + "version": "6.3.9", + "commands": [ + "fantomas" + ] } } } \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index 2bd711f..5259d7a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,8 +5,8 @@ indent_style = space indent_size = 4 charset = utf-8 trim_trailing_whitespace = true -insert_final_newline = false +insert_final_newline = true -[src/Client/*.fs] -# Feliz style -fsharp_single_argument_web_mode=true \ No newline at end of file +[*.{fs,fsx}] +fsharp_multiline_bracket_style = stroustrup +fsharp_newline_before_multiline_computation_expression = false diff --git a/README.md b/README.md index 0a144ff..90cfa4d 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,57 @@ Feliz-style bindings for [ag-grid-react](https://www.npmjs.com/package/ag-grid-react) -## Version Compatibility +## Versions -### The table below gives the ranges of compatible versions of Feliz AgGrid with its dependent packages. +### Required versions of dependencies -| Feliz.AgGrid | ag-grid-react/community | React | Fable | Feliz | -|- |- |- |- |- | -| 0.0.6 | 25.x | 17.x | 3.x | 1.x | -| 1.x | 31.x | 18.x | 4.x | 2.x | +Note: ag-grid-react/enterprise is only required if using enterprise features. + +| Feliz.AgGrid | ag-grid-react/community | ag-grid-react/enterprise | React | Fable | Feliz | +|--------------|-------------------------|--------------------------|-------|-------|-------| +| 2.x | 31.x | 31.x | 18.x | 4.x | 2.x | +| 1.x | 31.x | - | 18.x | 4.x | 2.x | +| 0.0.6 | 25.x | - | 17.x | 3.x | 1.x | + +### Migration guides + +#### v1 to v2 + +##### Import AG Grid style sheets + +To give you better control of your bundle, Feliz.AgGrid no longer imports styles for you, so you will need to include +appropriate imports yourself. For example, add the following lines to your client code to import the necessary styles +and the Balham theme. + +```fsharp +importAll "ag-grid-community/styles/ag-grid.css" +importAll "ag-grid-community/styles/ag-theme-balham.css" +``` + +##### Use new API + +Many properties have been updated to more closely reflect the AG Grid docs. In particular, functions like +valueFormatter, valueSetter and cellRenderer now take single-parameter functions, with that parameter having properties +on it allowing you to access the data that you need. This makes it easier for you to get started from the AG Grid docs. + +For example, rather than `ColumnDef.cellRenderer` giving you the value in the cell, you are now given a params object +that has a `.value` field. + +```diff +- ColumnDef.cellRenderer (fun x _ -> ++ ColumnDef.cellRenderer (fun rendererParams -> + Html.span [ + Html.span [ + prop.style [ style.fontSize 9 ] + prop.children [ Html.text "🏅" ] + ] +- Html.textf "%i" x ++ Html.text $"%i{rendererParams.value}" + ]) +``` + +You can see more examples of the required changes in [Git +commit `cbb0102`](https://github.com/CompositionalIT/feliz-ag-grid/commit/cbb0102e9a7504d0518d32999071c1751ea85be6). ## Installation @@ -21,7 +64,8 @@ Found in `./src` ## Demo -Demo code is in `./demo`. You can see it running live at [https://compositionalit.github.io/feliz-ag-grid/](https://compositionalit.github.io/feliz-ag-grid/) +Demo code is in `./demo`. You can see it running live +at [https://compositionalit.github.io/feliz-ag-grid/](https://compositionalit.github.io/feliz-ag-grid/) ## Publishing a new package diff --git a/demo/README.md b/demo/README.md index ae20e35..3c02b5f 100644 --- a/demo/README.md +++ b/demo/README.md @@ -8,13 +8,13 @@ ## Deploy - Change the reference to the src project into a reference to the appropriate NuGet package, e.g.: - ``` diff - - - - - - - - - - + - ``` + ```diff + + - + - + - + + + + + ``` - Run `npm run publish-docs` diff --git a/demo/src/Components.fs b/demo/src/Components.fs index 6441999..d148c69 100644 --- a/demo/src/Components.fs +++ b/demo/src/Components.fs @@ -9,7 +9,6 @@ open Thoth.Fetch open Thoth.Json open Fable.Core - let citDarkBlue = "#102035" type LinkData = { Text: string; Href: string } @@ -21,22 +20,20 @@ let container (children: ReactElement list) = style.flexDirection.column style.padding 50 style.maxWidth 1000 - style.margin (0,length.auto) + style.margin (0, length.auto) ] prop.children children ] let row (children: ReactElement list) = Html.div [ - prop.style [ - style.alignItems.center - style.display.flex - ] + prop.style [ style.alignItems.center; style.display.flex ] prop.children children ] let navbar () = - let logo : string = importDefault "./cit-logo.png" + let logo: string = importDefault "./cit-logo.png" + Html.div [ prop.style [ style.padding (0, 20) @@ -59,17 +56,9 @@ let navbar () = ] prop.children [ row [ - Html.img [ - prop.style [ - style.height 50 - ] - prop.src logo - ] + Html.img [ prop.style [ style.height 50 ]; prop.src logo ] Bulma.title [ - prop.style [ - style.color.white - style.fontSize (length.rem 1.5) - ] + prop.style [ style.color.white; style.fontSize (length.rem 1.5) ] prop.text "Compositional IT" ] ] @@ -81,7 +70,7 @@ let navbar () = let subHeading (label: string) = Bulma.subtitle [ prop.style [ - style.borderBottom(2, borderStyle.solid, citDarkBlue) + style.borderBottom (2, borderStyle.solid, citDarkBlue) style.marginTop 30 style.paddingBottom 10 ] @@ -106,18 +95,12 @@ let description (wrapperName: string) (wrappedComponent: string) links = Bulma.content [ Html.ul [ for l in links do - Html.li [ - link l - ] + Html.li [ link l ] ] ] ] -let headingWithContent (title: string) (children: ReactElement) = - Html.div [ - subHeading title - children - ] +let headingWithContent (title: string) (children: ReactElement) = Html.div [ subHeading title; children ] let codeBlock (code: string) = Html.pre [ @@ -130,174 +113,187 @@ let codeBlock (code: string) = prop.text code ] -type Olympian = - { Athlete: string - Age: int option - Country: string - Year: int - Date: string - Sport: string - Gold: int - Silver: int - Bronze: int - Total: int } +type Olympian = { + Athlete: string + Age: int option + Country: string + Year: int + Date: string + Sport: string + Gold: int + Silver: int + Bronze: int + Total: int +} + +importAll "ag-grid-community/styles/ag-grid.css" +importAll "ag-grid-community/styles/ag-theme-balham.css" [] let Demo () = - let (olympicData, setOlympicData) = React.useState(None) - let getData (): JS.Promise = - promise { - let url = sprintf "https://www.ag-grid.com/example-assets/olympic-winners.json" - return! Fetch.get(url, caseStrategy = CamelCase) - } + let olympicData, setOlympicData = React.useState None + + let getData () : JS.Promise = promise { + let url = "https://www.ag-grid.com/example-assets/olympic-winners.json" + return! Fetch.get (url, caseStrategy = CamelCase) + } React.useEffectOnce (fun () -> - let d = getData() - d.``then``(fun data -> - data - |> Some - |> setOlympicData) - |> ignore) + let d = getData () + d.``then`` (fun data -> data |> Some |> setOlympicData) |> ignore) let updateRowAthleteName newName row = olympicData |> Option.iter ( Array.map (fun x -> if x = row then { row with Athlete = newName } else x) >> Some - >> setOlympicData) + >> setOlympicData + ) container [ Html.div [ prop.style [ style.display.flex; style.flexWrap.wrap; style.flexDirection.column ] prop.children [ Html.div [ - description - "Feliz.AgGrid" - "ag-grid" - [ - { Text = "GitHub repo"; Href = "https://github.com/CompositionalIT/feliz-ag-grid" } - { Text = "NuGet package"; Href = "https://www.nuget.org/packages/Feliz.AgGrid" } - { Text = "Corresponding npm package"; Href = "https://www.npmjs.com/package/ag-grid-react" } - ] + description "Feliz.AgGrid" "ag-grid" [ + { + Text = "GitHub repo" + Href = "https://github.com/CompositionalIT/feliz-ag-grid" + } + { + Text = "NuGet package" + Href = "https://www.nuget.org/packages/Feliz.AgGrid" + } + { + Text = "Corresponding npm package" + Href = "https://www.npmjs.com/package/ag-grid-react" + } + ] headingWithContent "Demo" (match olympicData with - | Some olympicData -> - Html.div [ - prop.className ThemeClass.Balham - prop.children [ - AgGrid.grid [ - AgGrid.rowData olympicData - AgGrid.pagination true - AgGrid.defaultColDef [ - ColumnDef.resizable true - ColumnDef.sortable true - ] - AgGrid.domLayout AutoHeight - AgGrid.paginationPageSize 20 - AgGrid.onColumnGroupOpened (fun x -> x.AutoSizeGroupColumns()) - AgGrid.onGridReady (fun x -> x.AutoSizeAllColumns()) - AgGrid.singleClickEdit true - AgGrid.enableCellTextSelection true - AgGrid.ensureDomOrder true - AgGrid.columnDefs [ - ColumnDef.create [ - ColumnDef.filter RowFilter.Text - ColumnDef.headerName "Athlete (editable)" - ColumnDef.valueGetter (fun x -> x.Athlete) - ColumnDef.editable (fun _ _ -> true) - ColumnDef.valueSetter (fun newValue _ row -> updateRowAthleteName newValue row) - ] - ColumnDef.create [ - ColumnDef.filter RowFilter.Number - ColumnDef.columnType ColumnType.NumericColumn - ColumnDef.headerName "Age" - ColumnDef.valueGetter (fun x -> x.Age) - ColumnDef.valueFormatter (fun age _ -> - match age with - | Some age -> $"{age} years" - | None -> "Unknown" ) - ] - ColumnDef.create [ - ColumnDef.filter RowFilter.Text - ColumnDef.headerName "Country" - ColumnDef.valueGetter (fun x -> x.Country) - ] - ColumnDef.create [ - ColumnDef.filter RowFilter.Date - ColumnDef.headerName "Date" - ColumnDef.valueGetter (fun x -> - x.Date.Split("/") - |> function - | [| d; m; y |] -> DateTime(int y, int m, int d) - | _ -> DateTime.MinValue) - ColumnDef.valueFormatter (fun d _ -> d.ToShortDateString()) - ] - ColumnDef.create [ - ColumnDef.filter RowFilter.Text - ColumnDef.headerName "Sport" - ColumnDef.valueGetter (fun x -> x.Sport) - ] - ColumnGroup.create [ - ColumnGroup.headerName "Medal" - ColumnGroup.marryChildren true - ColumnGroup.openByDefault true - ] [ - ColumnDef.create [ - ColumnDef.filter RowFilter.Number - ColumnDef.headerName "Total" - ColumnDef.columnType ColumnType.NumericColumn - ColumnDef.valueGetter (fun x -> x.Total) - ColumnDef.cellRenderer (fun x _ -> - Html.span [ - Html.span [ - prop.style [ style.fontSize 9 ] - prop.children [ - Html.text "🏅" - ] - ] - Html.textf "%i" x - ]) - ColumnDef.columnGroupShow true - ] - ColumnDef.create [ - ColumnDef.filter RowFilter.Number - ColumnDef.headerName "Gold" - ColumnDef.columnType ColumnType.NumericColumn - ColumnDef.valueGetter (fun x -> x.Gold) - ColumnDef.columnGroupShow false - ] - ColumnDef.create [ - ColumnDef.filter RowFilter.Number - ColumnDef.headerName "Silver" - ColumnDef.columnType ColumnType.NumericColumn - ColumnDef.valueGetter (fun x -> x.Silver) - ColumnDef.columnGroupShow false - ] - ColumnDef.create [ - ColumnDef.filter RowFilter.Number - ColumnDef.headerName "Bronze" - ColumnDef.columnType ColumnType.NumericColumn - ColumnDef.valueGetter (fun x -> x.Bronze) - ColumnDef.columnGroupShow false - ] - ] - ] - ] - ] - ] - | None -> - Html.div []) + | Some olympicData -> + Html.div [ + prop.className ThemeClass.Balham + prop.children [ + AgGrid.grid [ + AgGrid.rowData olympicData + AgGrid.pagination true + AgGrid.defaultColDef [ ColumnDef.resizable true; ColumnDef.sortable true ] + AgGrid.domLayout AutoHeight + AgGrid.paginationPageSize 20 + AgGrid.onColumnGroupOpened (fun x -> x.AutoSizeGroupColumns()) + AgGrid.onGridReady (fun x -> x.AutoSizeAllColumns()) + AgGrid.singleClickEdit true + AgGrid.enableCellTextSelection true + AgGrid.ensureDomOrder true + AgGrid.columnDefs [ + ColumnDef.create [ + ColumnDef.filter RowFilter.Text + ColumnDef.headerName "Athlete (editable)" + ColumnDef.valueGetter (fun x -> x.Athlete) + ColumnDef.editable (fun _ _ -> true) + ColumnDef.valueSetter (fun valueChangedParams -> + updateRowAthleteName + valueChangedParams.newValue + valueChangedParams.data) + ] + ColumnDef.create [ + ColumnDef.filter RowFilter.Number + ColumnDef.columnType ColumnType.NumericColumn + ColumnDef.headerName "Age" + ColumnDef.valueGetter (fun x -> x.Age) + ColumnDef.valueFormatter (fun valueParams -> + match Option.flatten valueParams.value with + | Some age -> $"%i{age} years" + | None -> "Unknown") + ] + ColumnDef.create [ + ColumnDef.filter RowFilter.Text + ColumnDef.headerName "Country" + ColumnDef.valueGetter (fun x -> x.Country) + ] + ColumnDef.create [ + ColumnDef.filter RowFilter.Date + ColumnDef.headerName "Date" + ColumnDef.valueGetter (fun x -> + x.Date.Split("/") + |> function + | [| d; m; y |] -> DateTime(int y, int m, int d) + | _ -> DateTime.MinValue) + ColumnDef.valueFormatter (fun valueParams -> + valueParams.value + |> Option.map _.ToShortDateString() + |> Option.defaultValue "") + ] + ColumnDef.create [ + ColumnDef.filter RowFilter.Text + ColumnDef.headerName "Sport" + ColumnDef.valueGetter (fun x -> x.Sport) + ] + ColumnGroup.create [ + ColumnGroup.headerName "Medal" + ColumnGroup.marryChildren true + ColumnGroup.openByDefault true + ] [ + ColumnDef.create [ + ColumnDef.filter RowFilter.Number + ColumnDef.headerName "Total" + ColumnDef.columnType ColumnType.NumericColumn + ColumnDef.valueGetter (fun x -> x.Total) + ColumnDef.cellRenderer (fun rendererParams -> + match rendererParams.value with + | Some value -> + Html.span [ + Html.span [ + prop.style [ style.fontSize 9 ] + prop.children [ Html.text "🏅" ] + ] + Html.text $"%i{value}" + ] + | None -> React.fragment [] + ) + ColumnDef.columnGroupShow true + ] + ColumnDef.create [ + ColumnDef.filter RowFilter.Number + ColumnDef.headerName "Gold" + ColumnDef.columnType ColumnType.NumericColumn + ColumnDef.valueGetter (fun x -> x.Gold) + ColumnDef.columnGroupShow false + ] + ColumnDef.create [ + ColumnDef.filter RowFilter.Number + ColumnDef.headerName "Silver" + ColumnDef.columnType ColumnType.NumericColumn + ColumnDef.valueGetter (fun x -> x.Silver) + ColumnDef.columnGroupShow false + ] + ColumnDef.create [ + ColumnDef.filter RowFilter.Number + ColumnDef.headerName "Bronze" + ColumnDef.columnType ColumnType.NumericColumn + ColumnDef.valueGetter (fun x -> x.Bronze) + ColumnDef.columnGroupShow false + ] + ] + ] + ] + ] + ] + | None -> Html.div []) headingWithContent "Installation" - (codeBlock """ + (codeBlock + """ cd ./project -femto install Feliz.AgGrid""" ) +femto install Feliz.AgGrid""") headingWithContent "Sample Code" - (codeBlock """ + (codeBlock + """ open Feliz.AgGrid type Olympian = @@ -312,16 +308,16 @@ type Olympian = Bronze: int Total: int } +importAll "ag-grid-community/styles/ag-grid.css" +importAll "ag-grid-community/styles/ag-theme-balham.css" + Html.div [ prop.className ThemeClass.Balham prop.children [ AgGrid.grid [ AgGrid.rowData olympicData AgGrid.pagination true - AgGrid.defaultColDef [ - ColumnDef.resizable true - ColumnDef.sortable true - ] + AgGrid.defaultColDef [ ColumnDef.resizable true; ColumnDef.sortable true ] AgGrid.domLayout AutoHeight AgGrid.paginationPageSize 20 AgGrid.onColumnGroupOpened (fun x -> x.AutoSizeGroupColumns()) @@ -330,29 +326,32 @@ Html.div [ AgGrid.enableCellTextSelection true AgGrid.ensureDomOrder true AgGrid.columnDefs [ - ColumnDef.create [ + ColumnDef.create [ ColumnDef.filter RowFilter.Text ColumnDef.headerName "Athlete (editable)" ColumnDef.valueGetter (fun x -> x.Athlete) ColumnDef.editable (fun _ _ -> true) - ColumnDef.valueSetter (fun newValue _ row -> updateRowAthleteName newValue row) + ColumnDef.valueSetter (fun valueChangedParams -> + updateRowAthleteName + valueChangedParams.newValue + valueChangedParams.data) ] - ColumnDef.create [ + ColumnDef.create [ ColumnDef.filter RowFilter.Number ColumnDef.columnType ColumnType.NumericColumn ColumnDef.headerName "Age" ColumnDef.valueGetter (fun x -> x.Age) - ColumnDef.valueFormatter (fun age _ -> - match age with - | Some age -> $"{age} years" - | None -> "Unknown" ) + ColumnDef.valueFormatter (fun valueParams -> + match valueParams.value with + | Some age -> $"%i{age} years" + | None -> "Unknown") ] - ColumnDef.create [ + ColumnDef.create [ ColumnDef.filter RowFilter.Text ColumnDef.headerName "Country" ColumnDef.valueGetter (fun x -> x.Country) ] - ColumnDef.create [ + ColumnDef.create [ ColumnDef.filter RowFilter.Date ColumnDef.headerName "Date" ColumnDef.valueGetter (fun x -> @@ -360,9 +359,11 @@ Html.div [ |> function | [| d; m; y |] -> DateTime(int y, int m, int d) | _ -> DateTime.MinValue) - ColumnDef.valueFormatter (fun d _ -> d.ToShortDateString()) + ColumnDef.valueFormatter (fun valueParams -> + let date: DateTime = valueParams.value + date.ToShortDateString()) ] - ColumnDef.create [ + ColumnDef.create [ ColumnDef.filter RowFilter.Text ColumnDef.headerName "Sport" ColumnDef.valueGetter (fun x -> x.Sport) @@ -372,38 +373,36 @@ Html.div [ ColumnGroup.marryChildren true ColumnGroup.openByDefault true ] [ - ColumnDef.create [ + ColumnDef.create [ ColumnDef.filter RowFilter.Number ColumnDef.headerName "Total" ColumnDef.columnType ColumnType.NumericColumn ColumnDef.valueGetter (fun x -> x.Total) - ColumnDef.cellRenderer (fun x _ -> + ColumnDef.cellRenderer (fun rendererParams -> Html.span [ Html.span [ prop.style [ style.fontSize 9 ] - prop.children [ - Html.text "🏅" - ] + prop.children [ Html.text "🏅" ] ] - Html.textf "%i" x + Html.text $"%i{rendererParams.value}" ]) ColumnDef.columnGroupShow true ] - ColumnDef.create [ + ColumnDef.create [ ColumnDef.filter RowFilter.Number ColumnDef.headerName "Gold" ColumnDef.columnType ColumnType.NumericColumn ColumnDef.valueGetter (fun x -> x.Gold) ColumnDef.columnGroupShow false ] - ColumnDef.create [ + ColumnDef.create [ ColumnDef.filter RowFilter.Number ColumnDef.headerName "Silver" ColumnDef.columnType ColumnType.NumericColumn ColumnDef.valueGetter (fun x -> x.Silver) ColumnDef.columnGroupShow false ] - ColumnDef.create [ + ColumnDef.create [ ColumnDef.filter RowFilter.Number ColumnDef.headerName "Bronze" ColumnDef.columnType ColumnType.NumericColumn @@ -422,9 +421,4 @@ Html.div [ ] [] -let Documentation () = - Html.div [ - navbar() - Demo() - ] - +let Documentation () = Html.div [ navbar (); Demo() ] diff --git a/demo/src/Extensions.fs b/demo/src/Extensions.fs index f411780..f7ae0da 100644 --- a/demo/src/Extensions.fs +++ b/demo/src/Extensions.fs @@ -21,9 +21,11 @@ module Config = /// Tries to find the value of the configured variable if it is defined or returns a given default value otherwise. let variableOrDefault (key: string) (defaultValue: string) = let foundValue = variable key - if String.IsNullOrWhiteSpace foundValue - then defaultValue - else foundValue + + if String.IsNullOrWhiteSpace foundValue then + defaultValue + else + foundValue // Stylesheet API // let private stylehsheet = Stylesheet.load "./fancy.css" @@ -32,7 +34,7 @@ module Stylesheet = type IStylesheet = [] - abstract Item : className:string -> string + abstract Item: className: string -> string /// Loads a CSS module and makes the classes within available - let inline load (path: string) = importDefault path \ No newline at end of file + let inline load (path: string) = importDefault path diff --git a/demo/src/Main.fs b/demo/src/Main.fs index 2d17f0d..e969323 100644 --- a/demo/src/Main.fs +++ b/demo/src/Main.fs @@ -6,8 +6,6 @@ open Fable.Core.JsInterop importSideEffects "./styles/global.scss" -let root = ReactDOM.createRoot(document.getElementById "feliz-app") +let root = ReactDOM.createRoot (document.getElementById "feliz-app") -root.render( - App.Documentation() -) \ No newline at end of file +root.render (App.Documentation()) diff --git a/demo/webpack.config.js b/demo/webpack.config.js index 6e81c83..772fe6c 100644 --- a/demo/webpack.config.js +++ b/demo/webpack.config.js @@ -117,6 +117,11 @@ module.exports = { // - file-loader: Moves files referenced in the code (fonts, images) into output folder module: { rules: [ + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto' + }, { test: /\.(js|jsx)$/, exclude: /node_modules/, diff --git a/global.json b/global.json index 1b8195c..c19a2e0 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "6.0.100", + "version": "8.0.100", "rollForward": "latestMinor" } } diff --git a/src/AgGrid.fs b/src/AgGrid.fs index eb71e85..eb2c120 100644 --- a/src/AgGrid.fs +++ b/src/AgGrid.fs @@ -1,194 +1,790 @@ -// fsharplint:disable -module Feliz.AgGrid - -open System -open Fable.Core -open Fable.Core.JsInterop - -open Feliz - -let agGrid : obj = import "AgGridReact" "ag-grid-react" - -importAll "ag-grid-community/styles/ag-grid.css" -importAll "ag-grid-community/styles/ag-theme-alpine.css" -importAll "ag-grid-community/styles/ag-theme-balham.css" -importAll "ag-grid-community/styles/ag-theme-material.css" - -type RowSelection = Single | Multiple -type RowFilter = Number | Text | Date member this.FilterText = sprintf "ag%OColumnFilter" this -type DOMLayout = - Normal | AutoHeight | Print - member this.LayoutText = - match this with - | Normal -> "normal" - | AutoHeight -> "autoHeight" - | Print -> "print" - -module ThemeClass = - let Alpine = "ag-theme-alpine" - let AlpineDark = "ag-theme-alpine-dark" - let Balham = "ag-theme-balham" - let BalhamDark = "ag-theme-balham-dark" - let Material = "ag-theme-material" - -type MenuItemDef = { - name : string - action : unit -> unit - shortcut : string - icon : obj //HtmlElement -} -type MenuItem = - | BuiltIn of string - | Custom of MenuItemDef - -[] -type IColumnDefProp<'row, 'value> = interface end -let columnDefProp<'row, 'value> = unbox> -let columnDefProps<'row, 'value> = unbox> - -[] -type IColumnDef<'row> = interface end - -type ColumnType = RightAligned | NumericColumn - -let openClosed = function | true -> "open" | false -> "closed" - -[] -let CellRendererComponent<'value,'row> (render:'value -> 'row -> ReactElement, p) = - render p?value p?data - -[] -type ColumnDef<'row, 'value> = - static member inline autoComparator = columnDefProp<'row, 'value> ("comparator" ==> compare) - static member inline cellClass (setClass:'value -> 'row -> #seq) = columnDefProp<'row, 'value> ("cellClass" ==> fun p -> setClass p?value p?data |> Seq.toArray) - static member inline cellClassRules (rules: (string*('value -> 'row -> bool)) list) = columnDefProp<'row, 'value> ("cellClassRules" ==> (rules |> List.map (fun (className, rule) -> className ==> fun p -> rule p?value p?data) |> createObj)) - - [] - static member cellRendererFramework _ = failwith "cellRendererFramework isn't supported in the latest version of AgGrid. Use cellRenderer instead" - static member cellRenderer (render:'value -> 'row -> ReactElement) = columnDefProp<'row, 'value> ("cellRenderer" ==> fun p -> CellRendererComponent(render, p)) - static member inline cellStyle (setStyle:'value -> 'row -> _) = columnDefProp<'row, 'value> ("cellStyle" ==> fun p -> setStyle p?value p?data) - static member inline checkboxSelection (v:bool) = columnDefProp<'row, 'value> ("checkboxSelection" ==> v) - static member inline colId (v:string) = columnDefProp<'row, 'value> ("colId" ==> v) - static member inline columnGroupShow (v:bool) = columnDefProp<'row, 'value> ("columnGroupShow" ==> openClosed v) - static member inline columnType ct = columnDefProp<'row, 'value> ("type" ==> match ct with RightAligned -> "rightAligned" | NumericColumn -> "numericColumn") - static member inline comparator (callback: 'a -> 'a -> int) = columnDefProp<'row, 'value> ("comparator" ==> fun a b -> callback a b) - static member inline create<'v> (props:seq>) = props |> unbox<_ seq> |> createObj |> unbox> - static member inline editable (callback:'value -> 'row -> bool) = columnDefProp<'row, 'value> ("editable" ==> fun p -> callback p?value p?data) - static member inline field (v:'a -> string) = columnDefProp<'row, 'value> ("field" ==> v (unbox null)) - static member inline field (v:string) = columnDefProp<'row, 'value> ("field" ==> v) - static member inline filter (v:RowFilter) = columnDefProp<'row, 'value> ("filter" ==> v.FilterText) - static member inline headerCheckboxSelection (v:bool) = columnDefProp<'row, 'value> ("headerCheckboxSelection" ==> v) - static member inline headerClass (v:string) = columnDefProp<'row, 'value> ("headerClass" ==> v) - static member inline headerComponentFramework (callback:'colId -> 'props -> ReactElement) = columnDefProp<'row, 'value> ("headerComponentFramework" ==> fun p -> callback p?column?colId p) - static member inline headerName (v:string) = columnDefProp<'row, 'value> ("headerName" ==> v) - static member inline hide (v:bool) = columnDefProp<'row, 'value> ("hide" ==> v) - static member inline maxWidth (v:int) = columnDefProp<'row, 'value> ("maxWidth" ==> v) - static member inline minWidth (v:int) = columnDefProp<'row, 'value> ("minWidth" ==> v) - static member inline onCellClicked (handler:'value -> 'row -> unit) = columnDefProp<'row, 'value> ("onCellClicked" ==> (fun p -> handler p?value p?data)) - static member inline resizable (v:bool) = columnDefProp<'row, 'value> ("resizable" ==> v) - static member inline sortable (v:bool) = columnDefProp<'row, 'value> ("sortable" ==> v) - static member inline suppressKeyboardEvent callback = columnDefProp<'row, 'value> ("suppressKeyboardEvent" ==> fun x -> callback x?event) - static member inline suppressMovable = columnDefProp<'row, 'value> ("suppressMovable" ==> true) - static member inline valueFormatter (v:'value -> 'row -> string) = columnDefProp<'row, 'value> ("valueFormatter" ==> (fun p -> v p?value p?data)) - static member inline valueGetter (f:'row -> 'value) = columnDefProp<'row, 'value> ("valueGetter" ==> (fun x -> f x?data)) - static member inline valueSetter (f:string -> 'value -> 'row -> unit) = columnDefProp<'row, 'value> ("valueSetter" ==> (fun x -> f x?newValue x?oldValue x?data; false)) // return false to prevent the grid from immediately refreshing - static member inline width (v:int) = columnDefProp<'row, 'value> ("width" ==> v) - -[] -type IColumnGroupDefProp<'row> = interface end -let columnGroupDefProp<'row> = unbox> - -[] -type ColumnGroup<'row> = - static member inline headerName (v:string) = columnGroupDefProp<'row> ("headerName" ==> v) - static member inline marryChildren(v:bool) = columnGroupDefProp<'row> ("marryChildren" ==> v) - static member inline openByDefault(v:bool) = columnGroupDefProp<'row> ("openByDefault" ==> v) - - static member inline create<'row> (props:seq>) (children:seq>) = - props |> Seq.append [(columnGroupDefProp<'row> ("children" ==> Seq.toArray children))] |> unbox<_ seq> |> createObj |> unbox> - -[] -type IAgGridProp<'row> = interface end -let agGridProp<'row> (x:obj) = unbox> x - -[] -type AgGrid() = - static member inline onSelectionChanged (callback:'row array -> unit) = agGridProp<'row>("onSelectionChanged", fun x -> x?api?getSelectedRows() |> callback) - static member inline onCellValueChanged callback = agGridProp<'row>("onCellValueChanged", fun x -> callback x?data) - static member inline onRowClicked (handler:'value -> 'row -> unit) = agGridProp<'row> ("onRowClicked" ==> (fun p -> handler p?value p?data)) - static member inline singleClickEdit (v:bool) = agGridProp<'row>("singleClickEdit" ==> v) - static member inline rowDeselection (v:bool) = agGridProp<'row>("rowDeselection", v) - static member inline rowSelection (s:RowSelection) = agGridProp<'row>("rowSelection", s.ToString().ToLower()) - static member inline isRowSelectable (callback:'row -> bool) = agGridProp<'row>("isRowSelectable" ==> fun x -> x?data |> callback) - static member inline suppressRowClickSelection (v:bool) = agGridProp<'row>("suppressRowClickSelection" ==> v) - static member inline enableCellTextSelection (v:bool) = agGridProp<'row> ("enableCellTextSelection" ==> v) - static member inline ensureDomOrder (v:bool) = agGridProp<'row> ("ensureDomOrder" ==> v) - static member inline rowHeight (h:int) = agGridProp<'row>("rowHeight", h) - static member inline domLayout (l:DOMLayout) = agGridProp<'row>("domLayout", l.LayoutText) - static member inline immutableData (v:bool) = agGridProp<'row>("immutableData", v) - static member inline rowData (data:'row array) = agGridProp<'row>("rowData", data) - static member inline getRowNodeId (callback: 'row -> _) = agGridProp<'row>("getRowNodeId", callback) - static member inline columnDefs (columns:IColumnDef<'row> seq) = agGridProp<'row>("columnDefs", columns |> unbox |> Seq.toArray) - static member inline defaultColDef (defaults:IColumnDefProp<'row, 'value> seq) = agGridProp<'row>("defaultColDef", defaults |> unbox<_ seq> |> createObj) - static member onColumnGroupOpened (callback:_ -> unit) = // This can't be inline otherwise Fable produces invalid JS - let onColumnGroupOpened = fun ev -> - {| AutoSizeGroupColumns = fun () -> - // Runs the column autoSize in a 0ms timeout so that the cellRendererFramework cells render - // before the grid calculates how large each cell is - JS.setTimeout (fun () -> - let colIds = ev?columnGroup?children |> Array.map (fun x -> x?colId) - ev?columnApi?autoSizeColumns(colIds)) 0 |> ignore |} - |> callback - agGridProp<'row>("onColumnGroupOpened", onColumnGroupOpened) - - static member inline paginationPageSize (pageSize:int) = agGridProp<'row>("paginationPageSize", pageSize) - static member inline paginationAutoPageSize (v:bool) = agGridProp<'row>("paginationAutoPageSize", v) - static member inline pagination (v:bool) = agGridProp<'row>("pagination", v) - static member onGridReady (callback:_ -> unit) = // This can't be inline otherwise Fable produces invalid JS - let onGridReady = fun ev -> - {| AutoSizeAllColumns = - fun () -> - // Runs the column autoSize in a 0ms timeout so that the cellRendererFramework cells render - // before the grid calculates how large each cell is - JS.setTimeout (fun () -> - let colIds = ev?api?getColumns() |> Array.map (fun x -> x?colId) - ev?api?autoSizeColumns(colIds)) 0 |> ignore - Export = fun () -> ev?api?exportDataAsCsv(obj()) |} - |> callback - agGridProp<'row>("onGridReady", onGridReady) - static member inline enableRangeHandle (v: bool) = agGridProp<'row>("enableRangeHandle", v) - static member inline enableRangeSelection (v: bool) = agGridProp<'row>("enableRangeSelection", v) - static member inline getContextMenuItems (callback : int -> int -> MenuItem list) = agGridProp<'row>("getContextMenuItems", fun x -> - let menuItems = callback x?node?rowIndex x?column?colId - [| - for item in menuItems do - match item with - | BuiltIn builtInItemName -> box builtInItemName - | Custom customMenuItem -> box customMenuItem - |]) - static member inline headerHeight height = agGridProp<'row>("headerHeight", height) - static member inline onCellFocused callback = agGridProp<'row>("onCellFocused", fun x -> callback x?rowIndex x?column?colId) - static member inline onRangeSelectionChanged callback = agGridProp<'row>("onRangeSelectionChanged", fun x -> - let selectedRange = x?api?getCellRanges()?at(0) - let startRow = selectedRange?startRow?rowIndex - let startColumn = selectedRange?columns?at(0)?colId - let endRow = selectedRange?endRow?rowIndex - let endColumn = selectedRange?columns?at(selectedRange?columns?length-1)?colId - - callback startRow startColumn endRow endColumn) - static member inline popupParent parent = agGridProp<'row>("popupParent", parent) - static member inline processDataFromClipboard (callback : string[][] -> string[][]) = agGridProp<'row>("processDataFromClipboard", fun x -> callback x?data) - static member inline stopEditingWhenCellsLoseFocus (v: bool) = agGridProp<'row>("stopEditingWhenCellsLoseFocus", v) - static member inline stopEditingWhenGridLosesFocus (v: bool) = agGridProp<'row>("stopEditingWhenGridLosesFocus", v) - static member inline suppressClipboardApi (v: bool) = agGridProp<'row>("suppressClipboardApi", v) - static member inline suppressCopyRowsToClipboard (v: bool) = agGridProp<'row>("suppressCopyRowsToClipboard", v) - static member inline suppressCopySingleCellRanges (v: bool) = agGridProp<'row>("suppressCopySingleCellRanges", v) - static member inline suppressMultiRangeSelection (v: bool) = agGridProp<'row>("suppressMultiRangeSelection", v) - static member inline suppressRowHoverHighlight (v: bool) = agGridProp<'row>("suppressRowHoverHighlight", v) - static member inline suppressScrollOnNewData (v: bool) = agGridProp<'row>("suppressScrollOnNewData", v) - - static member inline key (v:string) = agGridProp<'row> (prop.key v) - static member inline key (v:int) = agGridProp<'row> (prop.key v) - static member inline key (v:System.Guid) = agGridProp<'row> (prop.key v) - - static member inline grid<'row> (props:IAgGridProp<'row> seq) = Interop.reactApi.createElement (agGrid, createObj !!props) +module Feliz.AgGrid + +open System + +open Fable.Core +open Fable.Core.JsInterop +open Feliz + +// Suppress unused value warnings - they are often necessary for Fable bindings. +#nowarn "1182" + +let agGrid: obj = import "AgGridReact" "ag-grid-react" + +[] +[] +type LicenseManager = + static member setLicenseKey(key: string) : unit = jsNative + +/// See https://www.ag-grid.com/react-data-grid/row-object/. +[] +type IRowNode<'row> = { + id: string + data: 'row + updateData: 'row -> unit + setData: 'row -> unit + setSelected: bool -> unit + rowIndex: int + rowTop: int + displayed: bool + isHovered: bool + isFullWidthCell: bool + isSelected: bool +} + +[] +type ICellRange = { + id: string + startRow: obj + endRow: obj +} with + + member this.startRowIndex: int = this.startRow?rowIndex + member this.endRowIndex: int = this.endRow?rowIndex + +/// See https://www.ag-grid.com/react-data-grid/grid-interface/#grid-api. +[] +type IGridApi<'row> = + abstract refreshCells: unit -> unit + abstract redrawRows: unit -> unit + abstract setGridOption: string -> obj -> unit + abstract getSelectedNodes: unit -> IRowNode<'row>[] + abstract getCellRanges: unit -> ICellRange[] + +/// See https://www.ag-grid.com/react-data-grid/column-object/. +[] +type IColumn = { getColId: unit -> string } + +[] +type IColumnDefProp<'row, 'value> = interface end + +let columnDefProp<'row, 'value> = unbox> + +// Although the AG Grid docs suggest that this should have two type params, we only give it one so that column defs +// with different underlying value types can be used in the same list (for example in AgGrid.columnDefs). +[] +type IColumnDef<'row> = interface end + +let columnDef<'row> = unbox> + +[] +module CallbackParams = + /// See https://www.ag-grid.com/react-data-grid/column-properties/#reference-editing-valueSetter. + /// See https://www.ag-grid.com/react-data-grid/column-properties/#reference-editing-valueParser. + [] + type IValueChangedParams<'row, 'value> = { + oldValue: 'value + newValue: 'value + node: IRowNode<'row> + data: 'row + column: IColumn + colDef: IColumnDef<'row> + api: IGridApi<'row> + } with + + member this.rowIndex = this.node.rowIndex + + /// See https://www.ag-grid.com/react-data-grid/cell-editors/#custom-components. + [] + type IValueParams<'row, 'value> = { + value: 'value option + data: 'row option + node: IRowNode<'row> + colDef: IColumnDef<'row> + column: IColumn + api: IGridApi<'row> + rowIndex: int + } + + /// See https://www.ag-grid.com/react-data-grid/grid-events/#reference-selection-cellFocused. + [] + type ICellFocusedEvent<'row> = { + api: IGridApi<'row> + rowIndex: int + column: IColumn + isFullWidthCell: bool + } + + /// See https://www.ag-grid.com/react-data-grid//grid-options/#reference-rowModels-getRowId. + [] + type IGetRowIdParams<'row> = { + data: 'row + level: int + parentKeys: string[] + api: IGridApi<'row> + context: obj + } + + [] + type ICellRendererParams<'row, 'value> = { + value: 'value option + data: 'row option + node: IRowNode<'row> + colDef: IColumnDef<'row> + column: IColumn + api: IGridApi<'row> + rowIndex: int + } + + [] + type IPasteEvent<'row> = { + source: string + api: IGridApi<'row> + context: obj + ``type``: string + } + + [] + type IProcessDataFromClipboardParams<'row> = { + data: string[][] + api: IGridApi<'row> + context: obj + } + +type RowSelection = + | Single + | Multiple + + + +[] +type RowFilter = + | Number + | Text + | Date + + member this.FilterText = sprintf "ag%OColumnFilter" this + +[] +type CellDataType = + | Text + | Number + | Date + | DateString + | Boolean + | Object + | Custom of string + + member this.CellDataTypeText = + match this with + | Text -> "text" + | Number -> "number" + | Date -> "date" + | DateString -> "dateString" + | Boolean -> "boolean" + | Object -> "object" + | Custom s -> s + +[] +type AgCellEditor = + | SelectCellEditor + | NumberCellEditor + | DateCellEditor + | DateStringCellEditor + | CheckboxCellEditor + | LargeTextCellEditor + | TextCellEditor + + member this.CellEditorText = sprintf "ag%O" this + +type DOMLayout = + | Normal + | AutoHeight + | Print + + member this.LayoutText = + match this with + | Normal -> "normal" + | AutoHeight -> "autoHeight" + | Print -> "print" + +module ThemeClass = + let Alpine = "ag-theme-alpine" + let AlpineDark = "ag-theme-alpine-dark" + let Balham = "ag-theme-balham" + let BalhamDark = "ag-theme-balham-dark" + let Material = "ag-theme-material" + +type ColumnType = + | RightAligned + | NumericColumn + +let openClosed = + function + | true -> "open" + | false -> "closed" + +[] +let CellRendererComponent<'row, 'value> + (render: ICellRendererParams<'row, 'value> -> ReactElement, p: ICellRendererParams<'row, 'value>) + = + render p + +[] +type ColumnDef<'row> = + // Constrain all props for a given column to be for the same value. + static member inline create<'value>(props: IColumnDefProp<'row, 'value> seq) = createObj !!props |> columnDef<'row> + + static member inline autoComparator() = + columnDefProp<'row, 'value> ("comparator" ==> compare) + + static member inline cellClass(setClass: 'value -> 'row -> #seq) = + columnDefProp<'row, 'value> ("cellClass" ==> fun p -> setClass p?value p?data |> Seq.toArray) + + static member inline cellClassRules(rules: (string * ('value -> 'row -> bool)) list) = + columnDefProp<'row, 'value> ( + "cellClassRules" + ==> (rules + |> List.map (fun (className, rule) -> className ==> fun p -> rule p?value p?data) + |> createObj) + ) + + static member cellDataType(v: bool) = + columnDefProp<'row, 'value> ("cellDataType" ==> v) + + static member cellDataType(v: CellDataType) = + columnDefProp<'row, 'value> ("cellDataType" ==> v.CellDataTypeText) + + [] + static member cellRendererFramework _ = + failwith "cellRendererFramework isn't supported in the latest version of AgGrid. Use cellRenderer instead" + + static member cellRenderer(render: ICellRendererParams<'row, 'value> -> ReactElement) = + columnDefProp<'row, 'value> ("cellRenderer" ==> fun p -> CellRendererComponent(render, p)) + + static member cellEditor(render: ICellRendererParams<'row, 'value> -> ReactElement) = + columnDefProp<'row, 'value> ("cellEditor" ==> fun p -> CellRendererComponent(render, p)) + + static member cellEditor(v: string) = + columnDefProp<'row, 'value> ("cellEditor" ==> v) + + static member cellEditor(v: AgCellEditor) = + columnDefProp<'row, 'value> ("cellEditor" ==> v.CellEditorText) + + static member cellEditorParams(v: string seq) = + columnDefProp<'row, 'value> ("cellEditorParams" ==> {| values = v |> Seq.toArray |}) + + static member cellEditorParams(v: obj) = + columnDefProp<'row, 'value> ("cellEditorParams" ==> v) + + static member cellEditorPopup(v: bool) = + columnDefProp<'row, 'value> ("cellEditorPopup" ==> v) + + static member inline cellStyle(setStyle: 'value -> 'row -> _) = + columnDefProp<'row, 'value> ("cellStyle" ==> fun p -> setStyle p?value p?data) + + static member inline checkboxSelection(v: bool) = + columnDefProp<'row, 'value> ("checkboxSelection" ==> v) + + static member inline colId(v: string) = + columnDefProp<'row, 'value> ("colId" ==> v) + + static member inline columnGroupShow(v: bool) = + columnDefProp<'row, 'value> ("columnGroupShow" ==> openClosed v) + + static member inline columnType ct = + columnDefProp<'row, 'value> ( + "type" + ==> match ct with + | RightAligned -> "rightAligned" + | NumericColumn -> "numericColumn" + ) + + static member inline comparator(callback: 'a -> 'a -> int) = + columnDefProp<'row, 'value> ("comparator" ==> fun a b -> callback a b) + + static member inline editable(callback: 'value -> 'row -> bool) = + columnDefProp<'row, 'value> ("editable" ==> fun p -> callback p?value p?data) + + static member inline editable(v: bool) = + columnDefProp<'row, 'value> ("editable" ==> v) + + static member inline equals(callback: 'value -> 'value -> bool) = + columnDefProp<'row, 'value> ("equals" ==> callback) + + static member inline enableRowGroup(v: bool) = + columnDefProp<'row, 'value> ("enableRowGroup" ==> v) + + static member inline enableCellChangeFlash(v: bool) = + columnDefProp<'row, 'value> ("enableCellChangeFlash" ==> v) + + static member inline field(v: string) = + columnDefProp<'row, 'value> ("field" ==> v) + + /// Usage: `ColumnDef.field _.FieldName` or `ColumnDef.field (fun x -> x.FieldName)` + static member inline field(f: 'row -> _) = + let idxOfFirstDot = (string f).IndexOf('.') + // `ColumnDef.field _.FirstName` and `ColumnDef.field (fun x -> x.FirstName)` both result in "FirstName". + let field = (string f).Substring(idxOfFirstDot + 1) + columnDefProp<'row, 'value> ("field" ==> field) + + static member inline filter(v: RowFilter) = + columnDefProp<'row, 'value> ("filter" ==> v.FilterText) + + static member inline filter(v: bool) = + columnDefProp<'row, 'value> ("filter" ==> v) + + static member inline floatingFilter(v: bool) = + columnDefProp<'row, 'value> ("floatingFilter" ==> v) + + static member inline headerCheckboxSelection(v: bool) = + columnDefProp<'row, 'value> ("headerCheckboxSelection" ==> v) + + static member inline headerClass(v: string) = + columnDefProp<'row, 'value> ("headerClass" ==> v) + + static member inline headerComponentFramework(callback: 'colId -> 'props -> ReactElement) = + columnDefProp<'row, 'value> ("headerComponentFramework" ==> fun p -> callback p?column?colId p) + + static member inline headerName(v: string) = + columnDefProp<'row, 'value> ("headerName" ==> v) + + static member inline wrapHeaderText(v: bool) = + columnDefProp<'row, 'value> ("wrapHeaderText" ==> v) + + static member inline autoHeaderHeight(v: bool) = + columnDefProp<'row, 'value> ("autoHeight" ==> v) + + static member inline hide(v: bool) = + columnDefProp<'row, 'value> ("hide" ==> v) + + static member inline maxWidth(v: int) = + columnDefProp<'row, 'value> ("maxWidth" ==> v) + + static member inline minWidth(v: int) = + columnDefProp<'row, 'value> ("minWidth" ==> v) + + static member inline onCellClicked(handler: 'value -> 'row -> unit) = + columnDefProp<'row, 'value> ("onCellClicked" ==> (fun p -> handler p?value p?data)) + + static member inline pinned(v: bool) = + columnDefProp<'row, 'value> ("pinned" ==> v) + + + + static member inline resizable(v: bool) = + columnDefProp<'row, 'value> ("resizable" ==> v) + + static member inline rowDrag(v: bool) = + columnDefProp<'row, 'value> ("rowDrag" ==> v) + + static member inline rowGroup(v: bool) = + columnDefProp<'row, 'value> ("rowGroup" ==> v) + + static member inline sortable(v: bool) = + columnDefProp<'row, 'value> ("sortable" ==> v) + + static member inline suppressKeyboardEvent callback = + columnDefProp<'row, 'value> ("suppressKeyboardEvent" ==> fun x -> callback x?event) + + static member inline suppressMovable() = + columnDefProp<'row, 'value> ("suppressMovable" ==> true) + + static member inline valueFormatter(callback: IValueParams<'row, 'value> -> string) = + columnDefProp<'row, 'value> ("valueFormatter" ==> callback) + + static member inline valueGetter(f: 'row -> 'value) = + columnDefProp<'row, 'value> ("valueGetter" ==> (fun (x:{|data: 'row option|}) -> x.data |> Option.map f )) + + static member inline valueSetter(f: IValueChangedParams<'row, 'value> -> unit) = + columnDefProp<'row, 'value> ("valueSetter" ==> f) + + static member inline valueSetter(f: IValueChangedParams<'row, 'value> -> bool) = + columnDefProp<'row, 'value> ("valueSetter" ==> f) + + static member inline valueParser(f: IValueChangedParams<'row, 'value> -> obj) = + columnDefProp<'row, 'value> ("valueParser" ==> f) // Is never called by AgGrid + + static member inline width(v: int) = + columnDefProp<'row, 'value> ("width" ==> v) + +[] +type IColumnGroupDefProp<'row> = interface end + +let columnGroupDefProp<'row> = unbox> + +[] +type ColumnGroup<'row> = + static member inline headerName(v: string) = + columnGroupDefProp<'row> ("headerName" ==> v) + + static member inline marryChildren(v: bool) = + columnGroupDefProp<'row> ("marryChildren" ==> v) + + static member inline openByDefault(v: bool) = + columnGroupDefProp<'row> ("openByDefault" ==> v) + + static member inline create (props: seq>) (children: seq>) = + let combinedProps = seq { + yield! props + columnGroupDefProp<'row> ("children" ==> Seq.toArray children) + } + + createObj !!combinedProps |> columnDef<'row> + +[] +type IAgGridProp<'row> = interface end + +let agGridProp<'row> (x: obj) = unbox> x + +[] +type AgGrid<'row> = + static member inline animateRows(v: bool) = agGridProp<'row> ("animateRows" ==> v) + + static member inline alwaysShowVerticalScroll(v: bool) = + agGridProp<'row> ("alwaysShowVerticalScroll" ==> v) + + static member inline columnDefs(columns: IColumnDef<'row> seq) = + agGridProp<'row> ("columnDefs", Seq.toArray !!columns) + + static member inline copyHeadersToClipboard(v: bool) = + agGridProp<'row> ("copyHeadersToClipboard" ==> v) + + static member inline domLayout(l: DOMLayout) = + agGridProp<'row> ("domLayout", l.LayoutText) + + static member inline enableCellTextSelection(v: bool) = + agGridProp<'row> ("enableCellTextSelection" ==> v) + + static member inline ensureDomOrder(v: bool) = + agGridProp<'row> ("ensureDomOrder" ==> v) + + static member inline enterNavigatesVertically(v: bool) = + agGridProp<'row> ("enterNavigatesVertically" ==> v) + + static member inline getRowNodeId(callback: 'row -> _) = + agGridProp<'row> ("getRowNodeId", callback) + + static member inline getRowId(callback: IGetRowIdParams<'row> -> string) = agGridProp<'row> ("getRowId", callback) + + static member inline onCellEditRequest(callback: obj -> unit) = + agGridProp<'row> ("onCellEditRequest", callback) + + static member inline onCellValueChanged callback = + agGridProp<'row> ("onCellValueChanged", (fun x -> callback x?data)) + + static member inline onPasteStart(callback: IPasteEvent<'row> -> unit) = + agGridProp<'row> ("onPasteStart", callback) + + static member inline onPasteEnd(callback: IPasteEvent<'row> -> unit) = + agGridProp<'row> ("onPasteEnd", callback) + + static member inline onRowClicked(handler: 'value -> 'row -> unit) = + agGridProp<'row> ("onRowClicked" ==> (fun p -> handler p?value p?data)) + + static member inline onSelectionChanged(callback: 'row array -> unit) = + agGridProp<'row> ("onSelectionChanged", (fun x -> x?api?getSelectedRows () |> callback)) + + static member inline readOnlyEdit(v: bool) = agGridProp<'row> ("readOnlyEdit" ==> v) + + static member inline singleClickEdit(v: bool) = + agGridProp<'row> ("singleClickEdit" ==> v) + + static member inline rowDeselection(v: bool) = agGridProp<'row> ("rowDeselection", v) + + static member inline rowSelection(s: RowSelection) = + agGridProp<'row> ("rowSelection", s.ToString().ToLower()) + + static member inline isRowSelectable(callback: 'row -> bool) = + agGridProp<'row> ("isRowSelectable" ==> fun x -> x?data |> callback) + + static member inline suppressRowClickSelection(v: bool) = + agGridProp<'row> ("suppressRowClickSelection" ==> v) + + static member inline rowHeight(h: int) = agGridProp<'row> ("rowHeight", h) + static member inline immutableData(v: bool) = agGridProp<'row> ("immutableData", v) + + /// Converts your data to a JS array to populate the grid. (This is less efficient than passing an array.) + static member inline rowData(data: 'row seq) = + agGridProp<'row> ("rowData", Seq.toArray data) + + static member inline rowData(data: 'row array) = agGridProp<'row> ("rowData", data) + + static member inline rowDragManaged(v: bool) = + agGridProp<'row> ("rowDragManaged" ==> v) + + static member inline defaultColDef(defaults: IColumnDefProp<'row, 'value> seq) = + agGridProp<'row> ("defaultColDef", defaults |> unbox<_ seq> |> createObj) + + static member onColumnGroupOpened(callback: _ -> unit) = // This can't be inline otherwise Fable produces invalid JS + let onColumnGroupOpened = + fun ev -> + {| + AutoSizeGroupColumns = + fun () -> + // Runs the column autoSize in a 0ms timeout so that the cellRenderer cells render before + // the grid calculates how large each cell is + JS.setTimeout + (fun () -> + let colIds = + ev?columnGroups + |> Seq.head + |> fun cg -> cg?children + |> Array.map (fun x -> x?colId) + + ev?api?autoSizeColumns colIds) + 0 + |> ignore + |} + |> callback + + agGridProp<'row> ("onColumnGroupOpened", onColumnGroupOpened) + + static member inline paginationPageSize(pageSize: int) = + agGridProp<'row> ("paginationPageSize", pageSize) + + static member inline paginationAutoPageSize(v: bool) = + agGridProp<'row> ("paginationAutoPageSize", v) + + static member inline pagination(v: bool) = agGridProp<'row> ("pagination", v) + + static member onGridReady(callback: _ -> unit) = // This can't be inline otherwise Fable produces invalid JS + let onGridReady = + fun ev -> + {| + AutoSizeAllColumns = + fun () -> + // Runs the column autoSize in a 0ms timeout so that the cellRendererFramework cells render + // before the grid calculates how large each cell is + JS.setTimeout + (fun () -> + let colIds = ev?api?getColumns () |> Array.map (fun x -> x?colId) + ev?api?autoSizeColumns colIds) + 0 + |> ignore + Export = fun () -> ev?api?exportDataAsCsv (obj ()) + |} + |> callback + + agGridProp<'row> ("onGridReady", onGridReady) + + static member inline processDataFromClipboard(callback: IProcessDataFromClipboardParams<'row> -> string[][]) = + agGridProp<'row> ("processDataFromClipboard", callback) + + static member inline enableRangeHandle(v: bool) = + agGridProp<'row> ("enableRangeHandle", v) + + static member inline enableRangeSelection(v: bool) = + agGridProp<'row> ("enableRangeSelection", v) + + + static member inline suppressAggFuncInHeader(v: bool) = + agGridProp<'row> ("suppressAggFuncInHeader", v) + + static member inline headerHeight height = + agGridProp<'row> ("headerHeight", height) + + static member inline groupHeaderHeight height = + agGridProp<'row> ("groupHeaderHeight", height) + + [] + static member inline onCellFocused callback = + agGridProp<'row> ("onCellFocused", (fun x -> callback (int x?rowIndex) (int x?column?colId))) + + static member inline onCellFocused callback = + agGridProp<'row> ("onCellFocused", (fun (e: ICellFocusedEvent<'row>) -> callback e)) + + static member inline onRangeSelectionChanged callback = + agGridProp<'row> ( + "onRangeSelectionChanged", + fun x -> + let selectedRange = x?api?getCellRanges ()?at 0 + let startRow = selectedRange?startRow?rowIndex + let startColumn = selectedRange?columns?at 0?colId + let endRow = selectedRange?endRow?rowIndex + let endColumn = selectedRange?columns?at (selectedRange?columns?length - 1)?colId + + callback startRow startColumn endRow endColumn + ) + + static member inline popupParent parent = + agGridProp<'row> ("popupParent", parent) + + static member inline processDataFromClipboard(callback: string[][] -> string[][]) = + agGridProp<'row> ("processDataFromClipboard", (fun x -> callback x?data)) + + static member inline stopEditingWhenCellsLoseFocus(v: bool) = + agGridProp<'row> ("stopEditingWhenCellsLoseFocus", v) + + static member inline stopEditingWhenGridLosesFocus(v: bool) = + agGridProp<'row> ("stopEditingWhenGridLosesFocus", v) + + static member inline suppressClipboardApi(v: bool) = + agGridProp<'row> ("suppressClipboardApi", v) + + static member inline suppressCopyRowsToClipboard(v: bool) = + agGridProp<'row> ("suppressCopyRowsToClipboard", v) + + static member inline suppressCopySingleCellRanges(v: bool) = + agGridProp<'row> ("suppressCopySingleCellRanges", v) + + static member inline suppressMultiRangeSelection(v: bool) = + agGridProp<'row> ("suppressMultiRangeSelection", v) + + static member inline suppressRowHoverHighlight(v: bool) = + agGridProp<'row> ("suppressRowHoverHighlight", v) + + static member inline suppressScrollOnNewData(v: bool) = + agGridProp<'row> ("suppressScrollOnNewData", v) + + static member inline key(v: string) = agGridProp<'row> (prop.key v) + static member inline key(v: int) = agGridProp<'row> (prop.key v) + static member inline key(v: Guid) = agGridProp<'row> (prop.key v) + + static member inline dataTypeDefinitions(v: obj) = + agGridProp<'row> ("dataTypeDefinitions", v) + + static member inline enableFillHandle(v: bool) = + agGridProp<'row> ("enableFillHandle", v) + + static member inline undoRedoCellEditing(v: bool) = + agGridProp<'row> ("undoRedoCellEditing", v) + + static member inline undoRedoCellEditingLimit(v: int) = + agGridProp<'row> ("undoRedoCellEditingLimit", v) + + static member inline grid(props: IAgGridProp<'row> seq) = + Interop.reactApi.createElement (agGrid, createObj !!props) + + module Enterprise = + + [] + type RowFilter = + | Number + | Text + | Date + | Set + | Multi + + member this.FilterText = sprintf "ag%OColumnFilter" this + + [] + type AgCellEditor = + | SelectCellEditor + | NumberCellEditor + | DateCellEditor + | DateStringCellEditor + | CheckboxCellEditor + | LargeTextCellEditor + | TextCellEditor + | RichSelectCellEditor + + member this.RichCellEditorText = sprintf "ag%O" this + + [] + type RowGroupingDisplayType = + | SingleColumn + | MultipleColumns + | GroupRows + | Custom + + member this.RowGroupingDisplayTypeText = + match this with + | SingleColumn -> "singleColumn" + | MultipleColumns -> "multipleColumns" + | GroupRows -> "groupRows" + | Custom -> "custom" + + [] + type RowGroupPanelShow = + | Always + | OnlyWhenGrouping + | Never + + member this.RowGroupPanelShowText = + match this with + | Always -> "always" + | OnlyWhenGrouping -> "onlyWhenGrouping" + | Never -> "never" + + [] + type AggregateFunction = + | Sum + | Min + | Max + | Count + | Avg + | First + | Last + + member this.AggregateText = (sprintf "%O" this).ToLower() + + type MenuItemDef = { + name: string + action: (unit -> unit) option + shortcut: string option + icon: obj option//HtmlElement + } + + [] + type BuiltInMenuItem = + | AutoSizeAll + | ExpandAll + | ContractAll + | Copy + | CopyWithHeaders + | CopyWithGroupHeaders + | Cut + | Paste + | ResetColumns + | Export + | CsvExport + | ExcelExport + | ChartRange + | PivotChart + + member this.BuiltInMenuItemText = + match this with + | AutoSizeAll -> "autoSizeAll" + | ExpandAll -> "expandAll" + | ContractAll -> "contractAll" + | Copy -> "copy" + | CopyWithHeaders -> "copyWithHeaders" + | CopyWithGroupHeaders -> "copyWithGroupHeaders" + | Cut -> "cut" + | Paste -> "paste" + | ResetColumns -> "resetColumns" + | Export -> "export" + | CsvExport -> "csvExport" + | ExcelExport -> "excelExport" + | ChartRange -> "chartRange" + | PivotChart -> "pivotChart" + + type MenuItem = + | BuiltIn of BuiltInMenuItem + | Custom of MenuItemDef + + [] + type ColumnDef<'row> = + static member inline filter(v: RowFilter) = columnDefProp<'row, 'value> ("filter" ==> v.FilterText) + static member cellEditor(v: AgCellEditor) = columnDefProp<'row, 'value> ("cellEditor" ==> v.RichCellEditorText) + static member inline pivot(v: bool) = columnDefProp<'row, 'value> ("pivot" ==> v) + static member inline aggFunc(v: AggregateFunction) = columnDefProp<'row, 'value> ("aggFunc" ==> v.AggregateText) + static member inline rowGroup(v: bool) = columnDefProp<'row, 'value> ("rowGroup" ==> v) + + [] + type AgGrid<'row> = + static member inline rowGroupPanelShow(v: RowGroupPanelShow) = + agGridProp<'row> ("rowGroupPanelShow", v.RowGroupPanelShowText) + + static member inline groupDisplayType(v: RowGroupingDisplayType) = + agGridProp<'row> ("groupDisplayType", v.RowGroupingDisplayTypeText) + + static member inline pivotMode(v: bool) = agGridProp<'row> ("pivotMode", v) + + static member inline getDataPath( v: 'row -> string array) = agGridProp<'row> ("getDataPath", v) + static member inline treeData(v: bool) = agGridProp<'row> ("treeData", v) + + static member inline getContextMenuItems(callback: int -> int -> MenuItem list) = + agGridProp<'row> ( + "getContextMenuItems", + fun x -> + let menuItems = callback x?node?rowIndex x?column?colId + + [| + for item in menuItems do + match item with + | BuiltIn builtInItemName -> box builtInItemName.BuiltInMenuItemText + | Custom customMenuItem -> box customMenuItem + |] + )