Building Config Driven UI Platforms

Parminder Singh
Lead UI Engineer @ Swiggy

Agenda

  • What is Config Driven Development (CDD)?

  • Example Case Study - Requirements Analysis

  • Mason UI Config JSON Walkthrough

  • Building Config Renderer in React 16.9.0 using Hooks

  • Live Exercise/Demo

  • Questions

Config Driven Development (CDD)?

  • Extending notion of  configuration files to execute code

  • Removes Code Duplication

  • Write various rules to execute business logic

  • Reduces development cost and effort

  • Build in generic manner and extend for specific use cases

  • Separation of concerns

  • Removes dependency for Deployment

Case Study

  • User interaction

  • State Management

  • Inter-dependencies amongst components

  • Fetching remote data sources on various events

  • Handling user input validations

 

 

 

Requirements

Web Components

@mollycule/mason

Installation

npm i @mollycule/mason -S
yarn add @mollycule/mason
const LoginFormPage = () => {

    const loginFormRenderer = new ReactConfigRenderer(
        config,
        new Map([
            ["TEXTFIELD", TextField],
            ["BUTTON", Button],
            ["BUTTON_PANEL", ButtonPanel]
        ]),
        {
            initialValues: new Map([["email", "paramsinghvc@swiggy.com"]]),
        }
    );

    const LoginForm = loginFormRenderer.render();

    return (
        <section>
            <LoginForm />
        </section>
    );
};

export default LoginFormPage;

Usage

@mollycule/mason

Node Configuration

{
  "id": "<Unique Component Id>",
  "type": "<Component Type>",
  "meta": {
    "disabled": false,
    "maxLength": 50,
    "placeholder": "Search here",
  },
  "show": true,
  "validations": [],
  "events": {},
  "data": {},    
  "style": {
    "display": "grid"
  },
  "children": [...]
}

@mollycule/mason

{
  "page": "FORM",
  "config": [
    {
      "id": "email",
      "type": "TEXTFIELD",
      "meta": {
        "placeholder": "Enter your email",
        "type": "email",
        "value": "",
        "autoComplete": "off"
      },
      "validations": [
        {
          "type": "REQUIRED"
        },
        {
          "type": "LENGTH",
          "meta": {
            "min": 3,
            "max": 50
          }
        },
        {
          "type": "REGEX",
          "meta": {
            "pattern": "^\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+$"
          }
        }
      ],
      "events": {
        "onChange": [
          {
            "type": "SET_VALUE"
          }
        ]
      },
      "style": {
        "marginBottom": 10
      }
    },
    {
      "id": "password",
      "type": "TEXTFIELD",
      "meta": {
        "value": "Password@123",
        "type": "password",
        "placeholder": "Enter your password"
      },
      "events": {
        "onChange": {
          "type": "SET_VALUE"
        }
      },
      "style": {
        "marginBottom": 10
      },
      "validations": [
        {
          "type": "REGEX",
          "meta": {
            "pattern": "^(?=.*\\d)(?=.*[!@#$%^&*])(?=.*[a-z])(?=.*[A-Z]).{8,}$"
          }
        }
      ]
    }
  ]
}

Event Handling

{
  "events": {
    "onChange": [
      {
        "type": "SET_VALUE"
      },
      {
        "type": "AJAX_CALL",
        "meta": {
          "endpoint": "https://api.edamam.com/search",
          "queryParams": {
            "q": "<%SELF%>",
            "app_id": "<APP_ID>"
          },
          "dataProcessor": "recipeDataSourceProcessor",
          "fieldId": "recipesListGroup"
        },
        "when": {
          "operator": "=",
          "leftOperand": "<%secondSearchInput%>",
          "rightOperand": "",
          "type": "ATOMIC"
        }
      }
    ],
    "onFocus": {
      "type": "SET_VALUE",
      "meta": {
        "value": [],
        "fieldId": "<fieldIdToSetValueFor>"
      }
    },
    "onClick": [
      {
        "type": "SET_DATASOURCE",
        "meta": {
          "data": [],
          "fieldId": "<fieldIdToSetDataSourceFor>"
        }
      },
      {
        "type": "CUSTOM",
        "meta": {
          "name": "<customFunctionNameHere>"
        }
      }
    ]
  }
}

Validations

{
  "type": "REQUIRED"
},

{
  "type": "REGEX",
  "meta": {
    "pattern": "^(?=.*\\d)(?=.*[!@#$%^&*])(?=.*[a-z])(?=.*[A-Z]).{8,}$"
  }
},

{
  "type": "RANGE",
  "meta": {
    "min": 3,
    "max": 10
  }
},

{
  "type": "LENGTH",
  "meta": {
    "min": 3,
    "max": 10
  }
},

{
  "type": "JSON"
},

{
  "type": "CUSTOM",
  "meta": {
    "name": "myCustomValidator"
  }
}
        
       

Custom Validator

        
new ReactConfigRenderer(
  config,
  new Map([...]),
  {
    validators: {
      myCustomEmailValidator(value: string) {
        return !value.includes("swiggy") ? "Not a valid swiggy email" : undefined;
      }
    }
  }
);

Conditional Logic

"show": {
  "type": "ATOMIC",
  "operator": "!=",
  "leftOperand": "<%statesDropdown%>",
  "rightOperand": "[]"
}
"disabled": {
  "type": "COMPOUND",
  "operator": "&&",
  "leftOperand": {
    "type": "ATOMIC",
    "operator": "!=",
    "leftOperand": "<%statesDropdown%>",
    "rightOperand": "[]"
  },
  "rightOperand": {
    "type": "ATOMIC",
    "operator": "!=",
    "leftOperand": "<%citiesDropdown%>",
    "rightOperand": "[]"
  }
}

Atomic

Compound

Exersice

Questions?

@paramsingh_66174

Thank You

Building Config Driven UI Platforms

By Param Singh

Building Config Driven UI Platforms

  • 1,694