module InputTable.View exposing (view) {-| Displays the InputTable @docs view -} import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (onClick, onInput) import Html.Keyed as Keyed import InputTable.Messages exposing (..) import InputTable.Model exposing (..) import InputTable.RowFilter as RowFilter import InputTable.ViewCell as ViewCell import SideBar.View import SideTable.View import Array import FocussedRow.View type alias Config rowData focussedData = { getRowAttributes : Maybe (Row rowData -> List (Attribute (TableMsg rowData focussedData))) , rowLink : Maybe String , currentPage : Int } {-| When passed the tableState, this will create the InputTable view -} view : Config rowData focussedData -> TableState rowData sideRowData -> Html (TableMsg rowData focussedData) view config tableState = let visibleColumns = List.filter .visible tableState.columns unfilteredRows = RowFilter.run tableState.rows visibleColumns tableState.searchText visibleRows = getPageRows tableState unfilteredRows tableButtonDropdownClass = if tableState.showVisibleColumnsUi then "table__button-dropdown table__button-dropdown--active" else "table__button-dropdown" sideBarHtml = case tableState.sideBarRowId of Just rowId -> SideBar.View.view tableState rowId False Nothing -> div [] [] sideBarClass = case tableState.sideBarRowId of Just state -> "sidebar sidebar--open" Nothing -> "sidebar" sideTableHtml = if tableState.showSideTable then SideTable.View.view tableState else text "" sideTableClass = if tableState.showSideTable then "sidebar sidebar--open sidebar--containing-table" else "sidebar" mainView = case tableState.focussedRow of Nothing -> tableView Just row -> [ FocussedRow.View.view row tableState ] tableView = [ div [ class "table__controls-wrapper" ] [ input [ placeholder "Search" , class "table__search" , value tableState.searchText , onInput SetSearchText ] [] , viewPageControls tableState unfilteredRows , (if tableState.hasSideTable then a [ class "button button--primary button--table" , onClick ToggleSideTable ] [ text "Select Reviewers" ] else text "" ) , a [ class "button button--tertiary button--table" , onClick ExportRows ] [ text "Export To Excel" ] , button [ class tableButtonDropdownClass, onClick ToggleChooseVisibleColumnsUi ] [ text "Choose Visible Columns" ] , viewChooseVisibleColumnButtons tableState ] , table [ class "table" ] [ thead [] (viewHeaders tableState visibleRows) , (if List.isEmpty tableState.rows then tr [] [ td [ class "table__all-columns-message" , colspan (List.filter .visible tableState.columns |> List.length |> (+) 1) ] [ text "There are currently no rows to display in this table" ] ] else if List.isEmpty visibleRows then tr [] [ td [ class "table__all-columns-message" , colspan (List.filter .visible tableState.columns |> List.length |> (+) 1) ] [ text "There are no rows that match your search query and filters" ] ] else Keyed.node "tbody" [] (List.concatMap (viewTableRow config tableState.rowsAreSelectable tableState.columns tableState.tableAction tableState.accordionColumns) visibleRows) ) , div [ class sideBarClass ] [ sideBarHtml ] , div [ class sideTableClass ] [ sideTableHtml ] ] , div [ class "table__controls-wrapper table__controls-wrapper--footer" ] [ input [ placeholder "Search" , class "table__search" , value tableState.searchText , onInput SetSearchText ] [] , viewPageControls tableState unfilteredRows ] ] in div [ onClick TableClick ] mainView getPageRows : TableState rowData sideRowData -> List (Row rowData) -> List (Row rowData) getPageRows { pageSize, currentPage } visibleRows = case pageSize of Nothing -> visibleRows Just pageSize -> let start = pageSize * (currentPage - 1) in visibleRows |> List.drop start |> List.take pageSize viewPageControls : TableState rowData sideRowData -> List (Row rowData) -> Html (TableMsg rowData focussedData) viewPageControls { pageSize, currentPage } visibleRows = case pageSize of Nothing -> text "" Just pageSize -> let pages = ((List.length visibleRows) // pageSize) |> (+) 1 |> List.range 1 |> List.indexedMap getPage getPage i p = let start = toString ((pageSize * i) + 1) end = toString (Basics.min (pageSize * (i + 1)) (List.length visibleRows)) in option [ selected ((i + 1) == currentPage), value <| toString <| i + 1 ] [ text (start ++ " - " ++ end) ] total = toString (List.length visibleRows) in if List.length pages == 1 then span [] [ text (total ++ (if List.length visibleRows == 1 then " row" else " rows" ) ) ] else span [ class "table__page-controls" ] [ span [ class "table__page-display" ] [ select [ onInput SetPage ] pages, span [] [ text (" of " ++ total) ] ] , div [ class "icon__wrapper" ] [ i [ class "icon icon--previous" , onClick PreviousPage ] [] , i [ class "icon icon--next" , onClick (NextPage (List.length visibleRows)) ] [] ] ] viewChooseVisibleColumnButtons : TableState rowData sideRowData -> Html (TableMsg rowData focussedData) viewChooseVisibleColumnButtons tableState = if tableState.showVisibleColumnsUi then div [ class "bar bar--table" ] (List.map (viewChooseVisibleColumnButton ToggleColumnVisibility) tableState.columns ) else text "" viewChooseVisibleColumnButton : (RowId -> TableMsg rowData focussedData) -> Column rowData -> Html (TableMsg rowData focussedData) viewChooseVisibleColumnButton message column = let extraClass = if column.visible then " bar__toggle--active" else "" in a [ class ("bar__toggle" ++ extraClass), onClick (message column.id) ] [ text column.name ] viewHeaders : TableState rowData sideRowData -> List (Row rowData) -> List (Html (TableMsg rowData focussedData)) viewHeaders model visibleRows = let accordionHeader_ = case model.accordionColumns of Nothing -> [] Just _ -> [ th [ class "table-header table-header__name" ] [ label [ class "table-cell--select-checkbox__label" ] [ div [] [ text "Expand Row" ] , checkbox (ToggleVisibleRowsExpansion visibleRows) (List.all .accordionExpanded visibleRows ) ] ] ] headers = if model.rowsAreSelectable then accordionHeader_ ++ ((checkboxHeader model visibleRows) :: (viewOtherHeaders model)) else accordionHeader_ ++ (viewOtherHeaders model) in [ tr [ class "table__header-row" ] headers ] checkboxHeader : TableState rowData sideRowData -> List (Row rowData) -> Html (TableMsg rowData focussedData) checkboxHeader model visibleRows = th [] [ label [ class "table-cell--select-checkbox__label" ] [ checkbox (ToggleVisibleRowsCheckboxes visibleRows) (List.filter (.checkboxDisabledMsg >> (==) Nothing) visibleRows |> List.all .checked ) ] ] checkbox : TableMsg rowData focussedData -> Bool -> Html (TableMsg rowData focussedData) checkbox message checkedVal = input [ type_ "checkbox", checked checkedVal, onClick message ] [] disabledCheckbox : String -> Html msg disabledCheckbox disabledMsg = input [ type_ "checkbox", disabled True ] [] viewOtherHeaders : TableState rowData sideRowData -> List (Html (TableMsg rowData focussedData)) viewOtherHeaders model = List.filterMap (viewHeader model.sorting) model.columns viewHeader : Sorting -> Column rowData -> Maybe (Html (TableMsg rowData focussedData)) viewHeader sorting column = if column.visible then let stringFilter config = input [ placeholder "Filter" , value config.filter , onInput (SetColumnFilterText column.id) ] [] boolFilter config = let ( nextFilter, filterText ) = case config.filter of Nothing -> ( Just True, "No Filter" ) Just True -> ( Just False, "Checked" ) Just False -> ( Nothing, "Unchecked" ) in a [ onClick (SwitchColumnCheckboxFilter column.id nextFilter) , class "table-header__bool-filter" ] [ text filterText ] sortingClass = case sorting of NoSorting -> "table-header__icon" Asc id -> if id == column.id then "table-header__icon--asc" else "table-header__icon" Desc id -> if id == column.id then "table-header__icon--desc" else "table-header__icon" ( filterElement, headerClass ) = (case column.subType of DisplayColumn props -> ( stringFilter props, "" ) LinkColumn props -> ( stringFilter props, "" ) TextColumn props -> ( stringFilter props, " table-header--control" ) DropdownColumn props -> ( stringFilter props, " table-header--control" ) MenuDropdownColumn props -> ( stringFilter props, " table-header--control" ) SubDropdownColumn props -> ( stringFilter props, " table-header--control" ) CheckboxColumn props -> ( boolFilter props, " table-header--control" ) SideBarButtonColumn props -> ( text "", " table-header--control" ) ) tooltipElement = if String.isEmpty column.description then span [] [] else span [ class "tooltip__box" ] [ text column.description ] in Just (th [ class ("table-header" ++ headerClass) ] [ div [ class "table-header__sort-wrapper", onClick (SortRows column) ] [ a [ class "table-header__name tooltip" ] [ text column.name , tooltipElement ] , i [ class ("table-header__icon " ++ sortingClass) ] [] ] , filterElement ] ) else Nothing viewTableRow : Config rowData focussedData -> Bool -> List (Column rowData) -> Maybe String -> Maybe (List (AccordionColumn rowData)) -> Row rowData -> List ( String, Html (TableMsg rowData focussedData) ) viewTableRow config rowsAreSelectable columns tableAction accordionColumns row = case ( accordionColumns, row.accordionRows, row.accordionExpanded ) of ( Just accordionColumns_, Just accordionRows, True ) -> [ viewNormalTableRow config rowsAreSelectable columns tableAction accordionColumns row , viewAccordionTableRow row accordionColumns_ accordionRows ] _ -> [ viewNormalTableRow config rowsAreSelectable columns tableAction accordionColumns row ] viewNormalTableRow : Config rowData focussedData -> Bool -> List (Column rowData) -> Maybe String -> Maybe (List (AccordionColumn rowData)) -> Row rowData -> ( String, Html (TableMsg rowData focussedData) ) viewNormalTableRow config rowsAreSelectable columns tableAction accordionColumns row = let accordionCell = case accordionColumns of Nothing -> [] Just _ -> [ td [] [ checkbox (ToggleAccordion row.id) row.accordionExpanded ] ] cells_ = if rowsAreSelectable then (checkboxCell row) :: (viewCells config columns row tableAction) else viewCells config columns row tableAction cells = accordionCell ++ cells_ getRowAttributes = Maybe.withDefault (\r -> []) config.getRowAttributes rowAttributes = [ onClick (FocusRow row), class "table__row--clickable" ] ++ (getRowAttributes row) in ( (toString row.id), tr rowAttributes cells ) viewAccordionTableRow : Row rowData -> List (AccordionColumn rowData) -> List (AccordionRow rowData) -> ( String, Html (TableMsg rowData focussedData) ) viewAccordionTableRow parentRow columns rows = let getHeaderHtml columnHeader = th [ class "table-header table-header__name" ] [ text columnHeader.name ] accordionHeadersHtml = List.map getHeaderHtml columns accordionHeadHtml = thead [] accordionHeadersHtml getRowHtml row = tr [] (List.map (getCellDataHtml row) columns ) getCellDataHtml row column = td [ class "table-cell" ] [ text (column.get row.data) ] accordionRowsHtml : List (Html (TableMsg rowData focussedData)) accordionRowsHtml = List.map getRowHtml rows in ( "accordion_" ++ parentRow.id , tr [] [ td [ colspan 100 ] [ table [ class "accordion__table" ] [ accordionHeadHtml , tbody [] accordionRowsHtml ] ] ] ) checkboxCell : Row rowData -> Html (TableMsg rowData focussedData) checkboxCell row = td [ class "table-cell table-cell--select-checkbox" ] [ label [ class "table-cell--select-checkbox__label" ] (case row.checkboxDisabledMsg of Nothing -> [ checkbox (ToggleRowCheckbox row.id) row.checked ] Just disabledMsg -> [ a [ class "tooltip" ] [ disabledCheckbox disabledMsg , span [ class "tooltip__box tooltip__box--bottom-right" ] [ text disabledMsg ] ] ] ) ] viewCells : Config rowData focussedData -> List (Column rowData) -> Row rowData -> Maybe String -> List (Html (TableMsg rowData focussedData)) viewCells config columns row tableAction = List.filterMap (ViewCell.view config.rowLink row tableAction config.currentPage) columns