Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic layout of React component from JSON

I am writing a react application that is a thin client UI for a financial trading application. A core requirement is that the application be completely dynamic and configurable, including forms. Specifically, I have trade entry forms that need to be defined on the server side and stored in the database to be rendered dynamically on the client but the layout is important and needs to be able to support multiple different formats. I have seen a few libraries that take JSON form schemas and create static forms from them but none of them seem to support the kind of layout flexibility I need. For example, I need to support tabs, columns, and rows of components. My question is - can anyone suggest a ReactJs library that can do what I'm looking for? If not, how might I go about implementing it myself?

Here's a more concrete example; Suppose I have a schema fetched from the server via a REST call that looks something like this:

{
title: "Securities Finance Trade Entry",
children: [
 {
   containerType: "tabs",
   children: [
      {
          title: "Common",
          children: [
             {
                containerType: "row", 
                children: [
                  {
                    input: "ComboBox",
                    label: "Trade Type",
                    options: ["Repo", "Buy/Sell", "FeeBased"]
                  },
                  {
                    input: "ComboBox",
                    label: "Direction",
                    options: ["Loan", "Borrow"]
                  }
                ]
             },
             {
               containerType: "row",
               children: [
                 {
                    containerType: "column",
                    children: [
                      {
                        containerType: "row",
                        children: [
                          {
                            input: "text",
                            label: "Book"
                          },
                          {
                            input: "text",
                            label: "Counterparty"
                          }
                        ]
                      },
                      {
                        containerType: "row",
                        children: [
                          {
                            input: "date",
                            label: "StartDate"
                          },
                          {
                            input: "date",
                            label: "EndDate"
                          }
                        ]
                      },
                      {
                        containerType: "row",
                        children: [
                          {
                            input: "text",
                            label: "Security"
                          },
                          {
                            input: "numeric",
                            label: "Quantity"
                          }
                        ]
                      }
                    ]
                 }
               ]
             }
          ]
      }
   ]
 }
]
} 

Which I expect to render something like: enter image description here Basically in that schema only one tab would be shown, but there could be multiple tabs, each containing multiple children in rows and columns and potentially nested containers of tabs too. If I were to render this myself in react I would think about using .map to iterate through the json and a number of if statements to insert tags where appropriate. However, items need to be nested so I don't know how to render it such that the tag chosen is dynamic and it can have children... For example I could write: { if (container.containerType === "column") { () } but then I'd somehow need to embed the rest of the controls inside that tag, I don't think I could just emit a () at the end...

Another option I've considered is translating the above json to JSX on the server side and sending that. It would be fairly easy to write a parser on the Java server side that converts the above json to a JSX document and return that to the client, but then how would I render it? Is there any way I could do something like:

onComponentMount() {
   fetch(webserviceUrl + 'getJsxForForm/' + props.formName)
   .then(result => {this.setState({form : result});
}
render()  {
   return ({this.state.form});
}

But again, I don't think this will work. If I fetch the document from the server it would render it just as plain text and not actually convert it to valid html, right?

So, what are my options? I'm looking for suggestions of an existing library that would do this, or suggestions on either of the other two approaches that I'm mentioned (would they work?, how could I do it?), or alternative ideas. Thanks, Troy

like image 721
Troy Peterson Avatar asked Nov 28 '18 14:11

Troy Peterson


1 Answers

I love the concept of dynamically rendering pages from some sort of JSON configuration.

The key will be defining Components to match containerTypes and inputs and then traversing your JSON configuration through a recursive function. In your JSON configuration, I suggest using component naming conventions wherever you want a component to be rendered. Hence, capitalizing Tabs, Row, Column, etc

Here is one example of that function. Note, in each of the containerType Components there is a call to this function with children being passed in.

See this pen: https://codepen.io/wesleylhandy/pen/oQaExK/

Example Component:

const Container = props => {
    return (
        <div className="container">
            <h1>{props.title}</h1>
            {renderChildren(props.children)}
        </div>
    )  
 }

Example Recursive-ish rendering of children

const renderChildren = children => {

    return children ? children.map((child, ind) => {
        const newChildren = child.children ? [...child.children] : [];
        const {containerType, title, input, label, options} = child
        let key;

        if (newChildren.length) {
            key = `${containerType}-${ind}`;
            switch (containerType) {
                case "Tabs":
                    return <Tabs 
                        key={key} 
                        title={title} 
                        children={newChildren}
                    />
                case "Column":
                    return <Column 
                        key={key} 
                        title={title} 
                       children={newChildren}
                    />
                case "Row":
                      return <Row 
                      key={key} 
                      title={title} 
                      children={newChildren}
                   /> 
                default:
                    return <Common 
                        key={key} 
                        title={title} 
                        children={newChildren}
                    />    
            }
        } else {
            key=`${input}-${ind}`
            switch (input) {
                case "ComboBox":
                    return <SelectGroup
                        key={key}
                        label={label}
                        options={options}
                   />
               case "Text":
               case "Date":
               case "Numeric":
                   return <InputGroup
                       key={key}
                       label={label}
                       type={input}
                   />           
           }
       }
   }) : null
}
like image 151
wlh Avatar answered Oct 25 '22 06:10

wlh