I want to add a Toggle to each row of the DynamicTable

I am using the latest version of the UI Kit. I understand that I can’t use the previous table in the latest UI Kit, and I have to use the DynamicTable. https://developer.atlassian.com/platform/forge/ui-kit/upgrade-to-ui-kit-latest/

How should I add a Toggle to each row of the DynamicTable?

Here is the sample code before adding the Toggle.

“C:\Forge\TestForm\src\resolvers\index.js”

import Resolver from '@forge/resolver';
import { Toggle } from '@forge/react';

const resolver = new Resolver();

resolver.define('getText', (req) => {
  console.log(req);
  return 'Please check';
});

export const handler = resolver.getDefinitions();

const createKey = (input) => {
  return input ? input.replace(/^(the|a|an)/, "").replace(/\s/g, "") : input;
}

export const presidents = [
  {
    id: 1,
    name: "name1",
    party: "party1",
    term: "2000-2010",
  },
  {
    id: 2,
    name: "name2",
    party: "party2",
    term: "2000-2010",
  },
  {
    id: 3,
    name: "name3",
    party: "party3",
    term: "2000-2010",
  },
  {
    id: 4,
    name: "name4",
    party: "party4",
    term: "2000-2010",
  },
  {
    id: 5,
    name: "name5",
    party: "party5",
    term: "2000-2010",
  },
  {
    id: 6,
    name: "name6",
    party: "party6",
    term: "2000-2010",
  },
  {
    id: 7,
    name: "name7",
    party: "party7",
    term: "2000-2010",
  },
  {
    id: 8,
    name: "name8",
    party: "party8",
    term: "2000-2010",
  },
  {
    id: 9,
    name: "name9",
    party: "party9",
    term: "2000-2010",  },
  {
    id: 10,
    name: "name10",
    party: "party10",
    term: "2000-2010",
  }
];

export const rows = presidents.map((president, index) => ({
  key: `row-${index}-${president.name}`,
  cells: [
    {
      key: createKey(president.name),
      content: president.name,
    },
    {
      key: createKey(president.party),
      content: president.party,
    },
    {
      key: president.id,
      content: president.term,
    }
  ],
}));

export const head = {
  cells: [
    {
      key: "name",
      content: "name",
      isSortable: true,
    },
    {
      key: "party",
      content: "party",
      shouldTruncate: true,
      isSortable: true,
    },
    {
      key: "term",
      content: "term",
      shouldTruncate: true,
      isSortable: true,
    },
  ],
};

resolver.define('getHead', (req) => {
  console.log(req);
  return head;
});

resolver.define('getRows', (req) => {
  console.log(req);
  return rows;
});

“C:\Forge\TestForm\src\frontend\index.jsx”

import React, { useEffect, useState } from 'react';
import ForgeReconciler, {Text, DynamicTable, Toggle , Stack ,Button, ButtonGroup} from '@forge/react';
import { invoke } from '@forge/bridge';

const App = () => {
  const [data, setData] = useState(null);
  const [gethead, setHead] = useState(null);
  const [getrows, setRows] = useState(null);
  useEffect(() => {
    invoke('getText', { example: 'my-invoke-variable' }).then(setData);
    invoke('getHead', { example: 'my-invoke-variable' }).then(setHead);
    invoke('getRows', { example: 'my-invoke-variable' }).then(setRows);

  }, []);
  
  return (
    <>
      <Text>{data ? data : 'Loading...'}</Text>
      <DynamicTable
      caption="Checklist"
      head={gethead}
      rows={getrows}
      rowsPerPage={5}
      highlightedRowIndex={[0,2,4]}
      />
      <Toggle id="toggle-default" />
      <Stack space="space.050" alignInline="end">
        <ButtonGroup label="Default button group">
          <Button>Cancel</Button>
          <Button appearance="primary">Submit</Button>
        </ButtonGroup>
      </Stack>
    </>
  );
  
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

1 Like

Hey @DTS ,

Apologies for the slow reply.

I’ve been working on a new sample app to reproduce the Custom UI todo list app. This app uses a dynamic table, and includes checkboxes to change the status to done - you could replace the checkboxes with the Toggle to show a toggle for each row.

import ForgeReconciler, { Button, Checkbox, DynamicTable, Inline,  Lozenge, Textfield } from '@forge/react';
import { invoke } from '@forge/bridge';

const App = () => {
  const [todo, setTodo] = useState(null);
  const [todoRows, setTodoRows] = useState(null);
  const [lozenge, setLozenge] = useState(null);

  useEffect(() => {
    invoke('get-all').then(setTodo);
  }, []);
  
  const doneLozenge = ( item ) => {
    if (item.done) {
      return (<Lozenge appearance="success">Done</Lozenge>);
    } else {
      return "";
    }
  }

  const addNewTodo = (data) => {
    if(data !== "") {
      setTodo([...todo, data])}
      else {
        console.log("new todo was blank")
      }
  }

  const create = (data) => {
    invoke('create', {data}).then(addNewTodo);
  }

  const remove = (data) => {
    setTodo(todo.filter(t => t.id !== data.id));
  }

  const update = (data) => {
    if(data !== "") {
      setTodo(todo.map(t => {if(t.id === data.id) {return data} else {return t}}));
    }
  }

  const fillTable = ( todos ) => {
    const lastRow = ({
      cells: [
       {
         colspan: 1,
         content: "",
       },
        {
          colspan: 10,
          content: <Textfield appearance="subtle"  spacing="compact" id="todo-input" placeholder="Add a todo +" onBlur={create}/>,
        },
        {
         colspan: 2,
         content: "",
         },
        {
          colspan: 1,
          content: ""
          },
      ],
     })
    if (todos.length > 0) {
      const rows = todos.map((item) => ({
       cells: [
         {
           colspan: 1,
           content: <Checkbox onChange={(event) => invoke('update', {item, event}).then(update)} defaultChecked={item.done}/>,
         },
         {
           colspan: 10,
           content: <Textfield appearance="subtle"  spacing="compact" defaultValue={item.title} onBlur={(event) => invoke('update', {item, event})}/>,
         },
         {
           colspan: 2, 
           content: doneLozenge(item),
         },
         {
           colspan: 1,
           content: <Button appearance="subtle" iconBefore="cross-circle" onClick={() => invoke('delete', {item}).then(remove)} spacing="compact"/>,
         },
       ],
     }))
     rows.push(lastRow)
     return rows;
    } else { 
      return [lastRow];  
    }
  }

  useEffect(() => {
    if (todo) {
      setTodoRows(fillTable(todo));
      const completedCount = todo.filter(t => t.done).length;
      const totalCount = todo.length;
      const text = completedCount + " / " + totalCount + " Completed";
      if(completedCount === totalCount) {
        setLozenge(<Lozenge appearance="success">{text}</Lozenge>);
      } else {
        setLozenge(<Lozenge>{text}</Lozenge>); 
      }
    } else {
      setTodo([])
    }
  }, [todo]);

  return (
    <>
      <DynamicTable 
        caption="Todos"
        rows={todoRows ? todoRows : []} />
      <Inline spread='space-between'>
        {lozenge}
        <Button onClick={() => invoke('delete-all').then(setTodo)}>Delete All</Button>
      </Inline>
    </>
  );
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

I hope this sample app helps! If it does please mark this reply as the solution.

Cheers!
Mel